diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index f1060acb..069b82b2 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -3,7 +3,7 @@ ## Current Focus - Phase: `docs/development/current/main/phases/phase-29ap/README.md` -- Next: Phase 29ap P13 (planned; see `docs/development/current/main/phases/phase-29ap/README.md`) +- Next: Phase 29ap P14 (planned; see `docs/development/current/main/phases/phase-29ap/README.md`) ## Gate (SSOT) diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index 9a24536e..98b7fc92 100644 --- a/docs/development/current/main/30-Backlog.md +++ b/docs/development/current/main/30-Backlog.md @@ -5,7 +5,7 @@ Scope: 「次にやる候補」を短く列挙するメモ。入口は `docs/dev ## Active -- Phase 29ap: `docs/development/current/main/phases/phase-29ap/README.md` (Next: P13 planned) +- Phase 29ap: `docs/development/current/main/phases/phase-29ap/README.md` (Next: P14 planned) - JoinIR regression gate SSOT: `docs/development/current/main/phases/phase-29ae/README.md` - CorePlan hardening (docs-first): `docs/development/current/main/phases/phase-29al/README.md` diff --git a/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md b/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md index b32b31fe..6a2612f6 100644 --- a/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md +++ b/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md @@ -34,7 +34,7 @@ Related: ## 1.1 Current (active) - Active phase: `docs/development/current/main/phases/phase-29ap/README.md` -- Next step: Phase 29ap P13 (planned) +- Next step: Phase 29ap P14 (planned) ## 2. すでに固めた SSOT(再発防止の土台) diff --git a/docs/development/current/main/phases/phase-29ap/README.md b/docs/development/current/main/phases/phase-29ap/README.md index b549fc32..3d00b09c 100644 --- a/docs/development/current/main/phases/phase-29ap/README.md +++ b/docs/development/current/main/phases/phase-29ap/README.md @@ -137,6 +137,15 @@ Gate (SSOT): - No change to logs or error strings. - Gate stays green. +## P13: Warning cleanup (dead_code) ✅ + +- Scope: + - Remove unused JoinIR lowering modules (legacy remnants). + - Trim unused Pattern4 extractor fields. +- Guardrails: + - No change to logs or error strings. + - Gate stays green. + ## Next (planned) -- P13: Dead-code cleanup (warnings-only, no behavior change) +- P14: Phase 29ap closeout (docs-only) diff --git a/src/mir/builder/control_flow/plan/extractors/pattern4.rs b/src/mir/builder/control_flow/plan/extractors/pattern4.rs index d64cb785..a571f59b 100644 --- a/src/mir/builder/control_flow/plan/extractors/pattern4.rs +++ b/src/mir/builder/control_flow/plan/extractors/pattern4.rs @@ -10,7 +10,6 @@ use super::common_helpers::{count_control_flow, has_break_statement, ControlFlow #[derive(Debug, Clone)] pub(crate) struct Pattern4Parts { pub loop_var: String, // Extracted from condition (i < n) - pub continue_count: usize, // Must be >= 1 for Pattern4 } /// Extract Pattern4 (Loop with Continue) parts @@ -70,10 +69,7 @@ pub(crate) fn extract_loop_with_continue_parts( // Phase 5: Return extracted parts // USER CORRECTION: No loop_var update validation - defer to Pattern4CarrierAnalyzer - Ok(Some(Pattern4Parts { - loop_var, - continue_count, - })) + Ok(Some(Pattern4Parts { loop_var })) } // ============================================================================ @@ -119,17 +115,17 @@ pub(crate) fn extract_pattern4_plan( use crate::mir::builder::control_flow::plan::{DomainPlan, Pattern4ContinuePlan}; // Step 1: Validate via existing extractor - let parts = extract_loop_with_continue_parts(condition, body)?; - if parts.is_none() { - return Ok(None); // Not Pattern4 → legacy fallback - } - - // Step 2: Extract loop variable from condition `i < N` - let loop_var = match extract_loop_condition_plan(condition)? { - Some((var, _bound)) => var, - None => return Ok(None), // Unsupported condition + let parts = match extract_loop_with_continue_parts(condition, body)? { + Some(parts) => parts, + None => return Ok(None), // Not Pattern4 → legacy fallback }; + // Step 2: Require loop condition `i < N` and re-use extracted loop_var + if extract_loop_condition_plan(condition)?.as_deref() != Some(parts.loop_var.as_str()) { + return Ok(None); // Unsupported condition + } + let loop_var = parts.loop_var; + // Step 3: Find continue condition from body let continue_cond = match find_continue_condition_plan(body)? { Some(cond) => cond, @@ -160,7 +156,7 @@ pub(crate) fn extract_pattern4_plan( } /// Extract loop condition: supports ` < ` only -fn extract_loop_condition_plan(cond: &ASTNode) -> Result, String> { +fn extract_loop_condition_plan(cond: &ASTNode) -> Result, String> { use crate::ast::{BinaryOperator, LiteralValue}; if let ASTNode::BinaryOp { operator, left, right, .. } = cond { @@ -174,13 +170,18 @@ fn extract_loop_condition_plan(cond: &ASTNode) -> Result, return Ok(None); }; - let bound = if let ASTNode::Literal { value: LiteralValue::Integer(n), .. } = right.as_ref() { - *n - } else { + let has_int_bound = matches!( + right.as_ref(), + ASTNode::Literal { + value: LiteralValue::Integer(_), + .. + } + ); + if !has_int_bound { return Ok(None); - }; + } - Ok(Some((var_name, bound))) + Ok(Some(var_name)) } else { Ok(None) } @@ -200,6 +201,8 @@ fn find_continue_condition_plan(body: &[ASTNode]) -> Result, Str Ok(None) } + + /// Extract carrier updates: ` = + ` pattern only fn extract_carrier_updates_plan( body: &[ASTNode], @@ -330,7 +333,7 @@ mod tests { let parts = parts.unwrap(); assert_eq!(parts.loop_var, "i"); - assert_eq!(parts.continue_count, 1); + assert_eq!(count_continue_statements_recursive(&body), 1); } #[test] @@ -383,7 +386,7 @@ mod tests { assert!(parts.is_some()); // Pattern4 extraction succeeds let parts = parts.unwrap(); assert_eq!(parts.loop_var, "i"); - assert_eq!(parts.continue_count, 1); + assert_eq!(count_continue_statements_recursive(&body), 1); } #[test] @@ -413,6 +416,6 @@ mod tests { let parts = parts.unwrap(); assert_eq!(parts.loop_var, "i"); - assert_eq!(parts.continue_count, 2); // Two continue statements detected + assert_eq!(count_continue_statements_recursive(&body), 2); // Two continue statements detected } } diff --git a/src/mir/join_ir/lowering/loop_with_continue_minimal/carrier_updates.rs b/src/mir/join_ir/lowering/loop_with_continue_minimal/carrier_updates.rs deleted file mode 100644 index b117a274..00000000 --- a/src/mir/join_ir/lowering/loop_with_continue_minimal/carrier_updates.rs +++ /dev/null @@ -1,174 +0,0 @@ -use crate::mir::join_ir::lowering::carrier_info::CarrierInfo; -use crate::mir::join_ir::lowering::debug_output_box::DebugOutputBox; -use crate::mir::join_ir::lowering::loop_update_analyzer::{UpdateExpr, UpdateRhs}; -use crate::mir::join_ir::{BinOpKind, JoinInst, MirLikeInst}; -use crate::mir::ValueId; -use std::collections::BTreeMap; - -pub(crate) fn emit_carrier_updates( - carrier_info: &CarrierInfo, - carrier_updates: &BTreeMap, - carrier_param_ids: &[ValueId], - carrier_next_ids: &[ValueId], - carrier_merged_ids: &[ValueId], - continue_cond: ValueId, - const_1: ValueId, - i_next: ValueId, - debug: &DebugOutputBox, - body: &mut Vec, -) -> Result<(), String> { - let carrier_count = carrier_info.carriers.len(); - debug_assert_eq!(carrier_param_ids.len(), carrier_count); - debug_assert_eq!(carrier_next_ids.len(), carrier_count); - debug_assert_eq!(carrier_merged_ids.len(), carrier_count); - - for idx in 0..carrier_count { - let carrier_param = carrier_param_ids[idx]; - let carrier_next = carrier_next_ids[idx]; - let carrier_merged = carrier_merged_ids[idx]; - let carrier_name = &carrier_info.carriers[idx].name; - - debug.log( - "carrier_update", - &format!("Processing carrier '{}' (idx={})", carrier_name, idx), - ); - let rhs = if let Some(update_expr) = carrier_updates.get(carrier_name) { - debug.log( - "carrier_update", - &format!("Found update expr: {:?}", update_expr), - ); - match update_expr { - UpdateExpr::BinOp { op, rhs, .. } => { - if *op != BinOpKind::Add { - debug.log( - "warn", - &format!( - "Carrier '{}' uses unsupported operator {:?}, defaulting to Add", - carrier_name, op - ), - ); - } - - match rhs { - UpdateRhs::Const(n) => { - if *n == 1 { - const_1 - } else { - debug.log( - "warn", - &format!( - "Carrier '{}' uses const {}, only const_1 is pre-allocated, using const_1", - carrier_name, n - ), - ); - const_1 - } - } - UpdateRhs::Variable(var_name) => { - if var_name == &carrier_info.loop_var_name { - debug.log( - "carrier_update", - &format!( - "Using i_next (ValueId({})) for variable '{}'", - i_next.0, var_name - ), - ); - i_next - } else { - debug.log( - "warn", - &format!( - "Carrier '{}' updates with unknown variable '{}', using const_1", - carrier_name, var_name - ), - ); - const_1 - } - } - UpdateRhs::NumberAccumulation { .. } => { - debug.log( - "phase190", - &format!( - "Carrier '{}' has number accumulation - not supported in Pattern 4, using Select passthrough", - carrier_name - ), - ); - body.push(JoinInst::Select { - dst: carrier_merged, - cond: continue_cond, - then_val: carrier_param, - else_val: carrier_param, - type_hint: None, - }); - continue; - } - UpdateRhs::StringLiteral(_) | UpdateRhs::Other => { - debug.log( - "phase178", - &format!( - "Carrier '{}' has string/complex update - skipping BinOp, using Select passthrough", - carrier_name - ), - ); - body.push(JoinInst::Select { - dst: carrier_merged, - cond: continue_cond, - then_val: carrier_param, - else_val: carrier_param, - type_hint: None, - }); - continue; - } - } - } - UpdateExpr::Const(n) => { - if *n == 1 { - const_1 - } else { - debug.log( - "warn", - &format!( - "Carrier '{}' uses const {}, only const_1 is pre-allocated, using const_1", - carrier_name, n - ), - ); - const_1 - } - } - } - } else { - debug.log( - "warn", - &format!( - "No update expression for carrier '{}', defaulting to +1", - carrier_name - ), - ); - const_1 - }; - - debug.log( - "carrier_update", - &format!( - "Generating: ValueId({}) = ValueId({}) + ValueId({})", - carrier_next.0, carrier_param.0, rhs.0 - ), - ); - body.push(JoinInst::Compute(MirLikeInst::BinOp { - dst: carrier_next, - op: BinOpKind::Add, - lhs: carrier_param, - rhs, - })); - - body.push(JoinInst::Select { - dst: carrier_merged, - cond: continue_cond, - then_val: carrier_param, - else_val: carrier_next, - type_hint: None, - }); - } - - Ok(()) -} diff --git a/src/mir/join_ir/lowering/loop_with_continue_minimal/condition_lowering.rs b/src/mir/join_ir/lowering/loop_with_continue_minimal/condition_lowering.rs deleted file mode 100644 index 23087da1..00000000 --- a/src/mir/join_ir/lowering/loop_with_continue_minimal/condition_lowering.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::ast::ASTNode; -use crate::mir::builder::MirBuilder; -use crate::mir::join_ir::lowering::carrier_info::CarrierInfo; -use crate::mir::join_ir::lowering::condition_env::ConditionEnv; -use crate::mir::join_ir::lowering::condition_lowering_box::{ - ConditionContext, ConditionLoweringBox, -}; -use crate::mir::join_ir::lowering::debug_output_box::DebugOutputBox; -use crate::mir::join_ir::lowering::error_tags; -use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer}; -use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv; -use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager; -use crate::mir::join_ir::JoinInst; -use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv; -use crate::mir::ValueId; - -fn make_scope_manager<'a>( - condition_env: &'a ConditionEnv, - body_local_env: Option<&'a LoopBodyLocalEnv>, - captured_env: Option<&'a CapturedEnv>, - carrier_info: &'a CarrierInfo, -) -> Pattern2ScopeManager<'a> { - Pattern2ScopeManager { - condition_env, - loop_body_local_env: body_local_env, - captured_env, - carrier_info, - } -} - -pub(crate) fn lower_header_condition( - condition: &ASTNode, - env: &ConditionEnv, - carrier_info: &CarrierInfo, - loop_var_name: &str, - loop_var_id: ValueId, - alloc_value: &mut dyn FnMut() -> ValueId, - current_static_box_name: Option<&str>, - debug: &DebugOutputBox, -) -> Result<(ValueId, Vec), String> { - let empty_body_env = LoopBodyLocalEnv::new(); - let empty_captured_env = CapturedEnv::new(); - let scope_manager = make_scope_manager( - env, - Some(&empty_body_env), - Some(&empty_captured_env), - carrier_info, - ); - - if !ExprLowerer::::is_supported_condition(condition) { - return Err(error_tags::lowering_error( - "pattern4/condition", - "ConditionLoweringBox does not support this condition (legacy path removed)", - )); - } - - let mut dummy_builder = MirBuilder::new(); - let mut expr_lowerer = - ExprLowerer::new(&scope_manager, ExprContext::Condition, &mut dummy_builder); - - let mut context = ConditionContext { - loop_var_name: loop_var_name.to_string(), - loop_var_id, - scope: &scope_manager, - alloc_value, - current_static_box_name: current_static_box_name.map(|s| s.to_string()), - }; - - let value_id = expr_lowerer.lower_condition(condition, &mut context).map_err(|e| { - format!( - "[joinir/pattern4/phase244] ConditionLoweringBox failed on supported condition: {:?}", - e - ) - })?; - - let instructions = expr_lowerer.take_last_instructions(); - debug.log( - "phase244", - &format!( - "Header condition via ConditionLoweringBox: {} instructions", - instructions.len() - ), - ); - - Ok((value_id, instructions)) -} diff --git a/src/mir/join_ir/lowering/loop_with_continue_minimal/mod.rs b/src/mir/join_ir/lowering/loop_with_continue_minimal/mod.rs deleted file mode 100644 index f847d9bc..00000000 --- a/src/mir/join_ir/lowering/loop_with_continue_minimal/mod.rs +++ /dev/null @@ -1,557 +0,0 @@ -//! Phase 195-196: Pattern 4 (Loop with Continue) Lowerer -//! -//! Phase 195: Initial minimal implementation for single-carrier (sum only) -//! Phase 196: Extended to support multiple carrier variables (sum, count, etc.) -//! Phase 202-C: Unified dual counters into JoinValueSpace -//! -//! Target: apps/tests/loop_continue_pattern4.hako (single carrier) -//! apps/tests/loop_continue_multi_carrier.hako (multi carrier) -//! -//! Code (multi-carrier): -//! ```nyash -//! static box Main { -//! main() { -//! local i = 0 -//! local sum = 0 -//! local count = 0 -//! loop(i < 10) { -//! i = i + 1 -//! if (i % 2 == 0) { -//! continue -//! } -//! sum = sum + i -//! count = count + 1 -//! } -//! print(sum) // 25 -//! print(count) // 5 -//! return 0 -//! } -//! } -//! ``` -//! -//! Expected output: sum = 25 (1+3+5+7+9), count = 5 (five odd numbers) -//! -//! ## Design Notes (Phase 196) -//! -//! This lowerer now generates JoinIR for N carrier variables dynamically: -//! - Loop function takes (i, carrier1, carrier2, ...) as parameters -//! - Each carrier gets a Select instruction for continue vs normal path -//! - k_exit returns all carriers, ExitMeta maps each to its exit ValueId -//! -//! Key changes from Phase 195: -//! - CarrierInfo passed in to enumerate all carriers -//! - ExitMeta::multiple() instead of ExitMeta::single() -//! - Dynamic parameter/slot allocation based on carrier count -//! -//! ## Phase 202-C: JoinValueSpace Unification -//! -//! Previously used dual counters: -//! - `value_counter`: JoinIR internal ValueIds (0, 1, 2, ...) -//! - `join_value_counter`: Condition-only variables (carrier_count + 1, ...) -//! -//! Now unified via JoinValueSpace: -//! - **Param region (100+)**: ConditionEnv, CarrierInfo parameters -//! - **Local region (1000+)**: Const, BinOp, intermediate values -//! -//! This prevents ValueId collisions after remapping (Phase 201 design). -//! -//! Following the "80/20 rule" from CLAUDE.md - now generalizing after working. - -mod carrier_updates; -mod condition_lowering; -mod validation; - -use crate::ast::ASTNode; -use crate::mir::builder::MirBuilder; -use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta}; -use crate::mir::join_ir::lowering::condition_env::ConditionEnv; -use crate::mir::join_ir::lowering::debug_output_box::DebugOutputBox; -use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace; -use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; -use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr; -use crate::mir::join_ir::lowering::return_collector::ReturnInfo; // Phase 284 P1 -use crate::mir::join_ir::lowering::return_jump_emitter::emit_return_conditional_jump; // Phase 284 P1 -use crate::mir::join_ir::{ - BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst, - UnaryOp, -}; -use crate::mir::ValueId; -use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism - -use carrier_updates::emit_carrier_updates; -use condition_lowering::lower_header_condition; -use validation::validate_condition_scope; - -/// Lower Pattern 4 (Loop with Continue) to JoinIR -/// -/// # Phase 195-196: Pure JoinIR Fragment Generation -/// # Phase 202-C: JoinValueSpace Unification -/// -/// This version generates JoinIR using **JoinValueSpace-allocated ValueIds**. -/// It has NO knowledge of the host function's ValueId space. The boundary mapping -/// is handled separately via JoinInlineBoundary. -/// -/// ## Design Philosophy -/// -/// - **Box A**: JoinIR Frontend (doesn't know about host ValueIds) -/// - **Box B**: This function - converts to JoinIR with JoinValueSpace IDs -/// - **Box C**: JoinInlineBoundary - stores boundary info -/// - **Box D**: merge_joinir_mir_blocks - injects Copy instructions -/// -/// This clean separation ensures JoinIR lowerers are: -/// - Pure transformers (no side effects) -/// - Reusable (same lowerer works in any context) -/// - Testable (can test JoinIR independently) -/// -/// # Arguments -/// -/// * `_scope` - LoopScopeShape (reserved for future generic implementation) -/// * `condition` - Loop condition AST node (e.g., `i < end`) (Phase 169) -/// * `_builder` - MirBuilder for variable resolution (Phase 169) -/// * `carrier_info` - Phase 196: Carrier metadata for dynamic multi-carrier support -/// * `carrier_updates` - Phase 197: Update expressions for each carrier variable -/// * `join_value_space` - Phase 202-C: Unified JoinIR ValueId allocator (Local region: 1000+) -/// * `return_info` - Phase 284 P1: Optional return statement info for early exit -/// -/// # Returns -/// -/// * `Ok((JoinModule, ExitMeta))` - Successfully lowered to JoinIR with exit metadata -/// * `Err(String)` - Pattern not matched or lowering error -/// -/// # Boundary Contract (Phase 196 updated, Phase 202-C: JoinValueSpace) -/// -/// This function returns a JoinModule with: -/// - **Input slots**: Local region (1000+) = i_init, carrier values -/// - **Output slots**: k_exit returns all carrier values -/// - **Exit metadata**: ExitMeta containing all carrier bindings -/// - **Caller responsibility**: Create JoinInlineBoundary to map ValueIds -/// -/// # Phase 284 P1: Return Handling -/// -/// If `return_info` is provided, generates: -/// - A `k_return` function that returns the literal value -/// - Conditional jump to k_return when return condition is met -pub(crate) fn lower_loop_with_continue_minimal( - _scope: LoopScopeShape, - condition: &ASTNode, - _builder: &mut MirBuilder, - carrier_info: &CarrierInfo, - carrier_updates: &BTreeMap, // Phase 222.5-D: HashMap → BTreeMap for determinism - join_value_space: &mut JoinValueSpace, - return_info: Option<&ReturnInfo>, // Phase 284 P1 - #[cfg(feature = "normalized_dev")] binding_map: Option< - &std::collections::BTreeMap, - >, -) -> Result<(JoinModule, ExitMeta), String> { - // Phase 170-D-impl-3: Validate that loop condition only uses supported variable scopes - // LoopConditionScopeBox checks that loop conditions don't reference loop-body-local variables - let loop_var_name = carrier_info.loop_var_name.clone(); - let loop_cond_scope = validate_condition_scope(&_scope, condition, &loop_var_name)?; - - let debug = DebugOutputBox::new_dev("joinir/pattern4"); - debug.log( - "phase170d", - &format!( - "Condition variables verified: {:?}", - loop_cond_scope.var_names() - ), - ); - - let mut join_module = JoinModule::new(); - let carrier_count = carrier_info.carriers.len(); - - debug.log( - "phase202c", - &format!( - "Generating JoinIR for {} carriers: {:?}", - carrier_count, - carrier_info - .carriers - .iter() - .map(|c| &c.name) - .collect::>() - ), - ); - - // ================================================================== - // Function IDs allocation - // ================================================================== - let main_id = JoinFuncId::new(0); - let loop_step_id = JoinFuncId::new(1); - let k_exit_id = JoinFuncId::new(2); - // Phase 284 P1: k_return for early return handling - let k_return_id = JoinFuncId::new(3); - - // ================================================================== - // ValueId allocation (Phase 202-C: Dynamic based on carrier count) - // ================================================================== - // Phase 286 P1: Function parameters MUST use alloc_param() (Param region: 100-999) - // Local variables use alloc_local() (Local region: 1000+) - - // main() parameters: [i_init, carrier1_init, carrier2_init, ...] - let i_init = join_value_space.alloc_param(); // PARAM region - let mut carrier_init_ids: Vec = Vec::new(); - for _ in 0..carrier_count { - carrier_init_ids.push(join_value_space.alloc_param()); // PARAM region - } - let loop_result = join_value_space.alloc_local(); // result from loop_step (LOCAL region) - - // loop_step() parameters: [i_param, carrier1_param, carrier2_param, ...] - let i_param = join_value_space.alloc_param(); // PARAM region - let mut carrier_param_ids: Vec = Vec::new(); - for _ in 0..carrier_count { - carrier_param_ids.push(join_value_space.alloc_param()); // PARAM region - } - - // k_exit() parameters: [carrier1_exit, carrier2_exit, ...] - // Phase 286 P1: Function parameters MUST use alloc_param() (PARAM region: 100-999) - let mut carrier_exit_ids: Vec = Vec::new(); - for _ in 0..carrier_count { - carrier_exit_ids.push(join_value_space.alloc_param()); // PARAM region - } - - // Phase 202-C: Build ConditionEnv for condition lowering using JoinValueSpace - // Loop variable and condition-only variables use Param region (100+) - use crate::mir::join_ir::lowering::condition_to_joinir::extract_condition_variables; - let mut env = ConditionEnv::new(); - - // Add loop parameter to env (using i_param which is in Local region) - let loop_var_name = carrier_info.loop_var_name.clone(); - env.insert(loop_var_name.clone(), i_param); - - // Extract and add condition-only variables (Param region) - let condition_var_names = extract_condition_variables(condition, &[loop_var_name.clone()]); - let mut condition_var_join_ids = std::collections::BTreeMap::new(); - for var_name in &condition_var_names { - let join_id = join_value_space.alloc_param(); - env.insert(var_name.clone(), join_id); - condition_var_join_ids.insert(var_name.clone(), join_id); - } - - // Phase 80-C (P2): Register BindingIds for condition variables (dev-only) - #[cfg(feature = "normalized_dev")] - if let Some(binding_map) = binding_map { - let debug = DebugOutputBox::new_dev("phase80/p4"); - - // Register loop variable BindingId - if let Some(bid) = binding_map.get(&loop_var_name) { - env.register_loop_var_binding(*bid, i_param); - #[cfg(debug_assertions)] - debug.log( - "register", - &format!( - "Registered loop var '{}' BindingId({}) -> ValueId({})", - loop_var_name, bid.0, i_param.0 - ), - ); - } - - // Register condition binding BindingIds - for (var_name, join_id) in &condition_var_join_ids { - if let Some(bid) = binding_map.get(var_name) { - env.register_condition_binding(*bid, *join_id); - #[cfg(debug_assertions)] - debug.log( - "register", - &format!( - "Registered condition binding '{}' BindingId({}) -> ValueId({})", - var_name, bid.0, join_id.0 - ), - ); - } - } - } - - // Phase 202-C: Create allocator closure AFTER all direct allocations - // This avoids borrow checker issues - let mut alloc_value = || join_value_space.alloc_local(); - - // Phase 169 / Phase 171-fix / Phase 244: Lower condition using ConditionLoweringBox trait - let (cond_value, mut cond_instructions) = lower_header_condition( - condition, - &env, - carrier_info, - &loop_var_name, - i_param, - &mut alloc_value, - None, - &debug, - )?; - - // Loop control temporaries - let exit_cond = alloc_value(); - let const_1 = alloc_value(); - let i_next = alloc_value(); - let const_2 = alloc_value(); - let remainder = alloc_value(); - let const_0 = alloc_value(); - let continue_cond = alloc_value(); - - // Per-carrier: next value and merged value (after Select) - let mut carrier_next_ids: Vec = Vec::new(); - let mut carrier_merged_ids: Vec = Vec::new(); - for _ in 0..carrier_count { - carrier_next_ids.push(alloc_value()); // carrier_next = carrier + ... - carrier_merged_ids.push(alloc_value()); // carrier_merged = Select(...) - } - - // ================================================================== - // main() function - // ================================================================== - // Phase 196: main() takes i and all carriers as parameters - let mut main_params = vec![i_init]; - main_params.extend(carrier_init_ids.iter().copied()); - - let mut main_func = JoinFunction::new(main_id, "main".to_string(), main_params.clone()); - - // result = loop_step(i_init, carrier1_init, carrier2_init, ...) - main_func.body.push(JoinInst::Call { - func: loop_step_id, - args: main_params.clone(), - k_next: None, - dst: Some(loop_result), - }); - - // return result - main_func.body.push(JoinInst::Ret { - value: Some(loop_result), - }); - - join_module.add_function(main_func); - - // ================================================================== - // loop_step(i, carrier1, carrier2, ...) function - // ================================================================== - let mut loop_step_params = vec![i_param]; - loop_step_params.extend(carrier_param_ids.iter().copied()); - - let mut loop_step_func = - JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_step_params); - - // ------------------------------------------------------------------ - // Natural Exit Condition Check (Phase 169: from AST) - // ------------------------------------------------------------------ - // Insert all condition evaluation instructions - loop_step_func.body.append(&mut cond_instructions); - - // Negate the condition for exit check: exit_cond = !cond_value - loop_step_func - .body - .push(JoinInst::Compute(MirLikeInst::UnaryOp { - dst: exit_cond, - op: UnaryOp::Not, - operand: cond_value, - })); - - // Jump(k_exit, [carrier1, carrier2, ...], cond=exit_cond) - // Phase 196: Pass ALL carrier params as exit values - loop_step_func.body.push(JoinInst::Jump { - cont: k_exit_id.as_cont(), - args: carrier_param_ids.clone(), - cond: Some(exit_cond), - }); - - // ------------------------------------------------------------------ - // Loop Body: i_next = i + 1 - // ------------------------------------------------------------------ - loop_step_func - .body - .push(JoinInst::Compute(MirLikeInst::Const { - dst: const_1, - value: ConstValue::Integer(1), - })); - - loop_step_func - .body - .push(JoinInst::Compute(MirLikeInst::BinOp { - dst: i_next, - op: BinOpKind::Add, - lhs: i_param, - rhs: const_1, - })); - - // ------------------------------------------------------------------ - // Phase 284 P1: Return Condition Check (if return_info present) - // ------------------------------------------------------------------ - // For fixture: if (i == 3) { return 7 } - // Condition is checked against i_next (after increment) - // - // Phase 284 P1 Refactor: Use emit_return_conditional_jump SSOT - emit_return_conditional_jump( - &mut loop_step_func, - return_info, - k_return_id, - i_next, - &loop_var_name, - &mut alloc_value, - &debug, - )?; - - // ------------------------------------------------------------------ - // Continue Condition Check: (i_next % 2 == 0) - // ------------------------------------------------------------------ - loop_step_func - .body - .push(JoinInst::Compute(MirLikeInst::Const { - dst: const_2, - value: ConstValue::Integer(2), - })); - - loop_step_func - .body - .push(JoinInst::Compute(MirLikeInst::BinOp { - dst: remainder, - op: BinOpKind::Mod, - lhs: i_next, - rhs: const_2, - })); - - loop_step_func - .body - .push(JoinInst::Compute(MirLikeInst::Const { - dst: const_0, - value: ConstValue::Integer(0), - })); - - loop_step_func - .body - .push(JoinInst::Compute(MirLikeInst::Compare { - dst: continue_cond, - op: CompareOp::Eq, - lhs: remainder, - rhs: const_0, - })); - - emit_carrier_updates( - carrier_info, - carrier_updates, - &carrier_param_ids, - &carrier_next_ids, - &carrier_merged_ids, - continue_cond, - const_1, - i_next, - &debug, - &mut loop_step_func.body, - )?; - - // ------------------------------------------------------------------ - // Tail call: loop_step(i_next, carrier1_merged, carrier2_merged, ...) - // ------------------------------------------------------------------ - let mut tail_call_args = vec![i_next]; - tail_call_args.extend(carrier_merged_ids.iter().copied()); - - loop_step_func.body.push(JoinInst::Call { - func: loop_step_id, - args: tail_call_args, - k_next: None, // CRITICAL: None for tail call - dst: None, - }); - - join_module.add_function(loop_step_func); - - // ================================================================== - // k_exit(carrier1_exit, carrier2_exit, ...) function - // ================================================================== - let mut k_exit_func = - JoinFunction::new(k_exit_id, "k_exit".to_string(), carrier_exit_ids.clone()); - - // For now, return the first carrier's exit value (or void if no carriers) - // TODO: Consider returning a tuple or using a different mechanism - let ret_value = if carrier_exit_ids.is_empty() { - None - } else { - Some(carrier_exit_ids[0]) - }; - - k_exit_func.body.push(JoinInst::Ret { value: ret_value }); - - join_module.add_function(k_exit_func); - - // ================================================================== - // Phase 284 P1: k_return() function for early return - // ================================================================== - if let Some(ret_info) = return_info { - let return_value_id = alloc_value(); - - let mut k_return_func = JoinFunction::new(k_return_id, "k_return".to_string(), vec![]); - - // Generate: return_value = Const(ret_info.value) - k_return_func - .body - .push(JoinInst::Compute(MirLikeInst::Const { - dst: return_value_id, - value: ConstValue::Integer(ret_info.value), - })); - - // Generate: Ret(return_value) - k_return_func.body.push(JoinInst::Ret { - value: Some(return_value_id), - }); - - join_module.add_function(k_return_func); - - debug.log( - "phase284", - &format!( - "Generated k_return function: returns {}", - ret_info.value - ), - ); - } - - // Set entry point - join_module.entry = Some(main_id); - - debug.log_simple("Phase 202-C: Generated JoinIR for Loop with Continue Pattern"); - debug.log_simple("Functions: main, loop_step, k_exit"); - debug.log_simple("Continue: Select-based skip"); - debug.log_simple("ValueId allocation: JoinValueSpace (Local region 1000+)"); - debug.log( - "summary", - &format!( - "Carriers: {} ({:?})", - carrier_count, - carrier_info - .carriers - .iter() - .map(|c| c.name.as_str()) - .collect::>() - ), - ); - - // ================================================================== - // Phase 197-B: Build ExitMeta with carrier_param_ids (Jump arguments) - // ================================================================== - // Previously used carrier_exit_ids (k_exit parameters) which are not defined - // when MIR functions are merged. Now use carrier_param_ids which are the - // actual values passed to k_exit via Jump instruction. - // - // k_exit receives carrier values via: Jump(k_exit, [carrier_param1, carrier_param2, ...]) - // These carrier_param_ids are defined in loop_step and properly remapped. - let mut exit_values: Vec<(String, ValueId)> = Vec::new(); - for (idx, carrier) in carrier_info.carriers.iter().enumerate() { - // Phase 197-B: Use carrier_param_ids instead of carrier_exit_ids - let exit_id = carrier_param_ids[idx]; - exit_values.push((carrier.name.clone(), exit_id)); - debug.log( - "exit_meta", - &format!( - "ExitMeta: {} → ValueId({}) (carrier_param)", - carrier.name, exit_id.0 - ), - ); - } - - let exit_meta = ExitMeta::multiple(exit_values); - - debug.log( - "phase169", - &format!( - "ExitMeta total: {} bindings (condition from AST)", - exit_meta.exit_values.len() - ), - ); - - Ok((join_module, exit_meta)) -} diff --git a/src/mir/join_ir/lowering/loop_with_continue_minimal/validation.rs b/src/mir/join_ir/lowering/loop_with_continue_minimal/validation.rs deleted file mode 100644 index 483de9e0..00000000 --- a/src/mir/join_ir/lowering/loop_with_continue_minimal/validation.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::ast::ASTNode; -use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; -use crate::mir::loop_pattern_detection::error_messages::{ - extract_body_local_names, format_unsupported_condition_error, -}; -use crate::mir::loop_pattern_detection::loop_condition_scope::{LoopConditionScope, LoopConditionScopeBox}; - -pub(crate) fn validate_condition_scope( - scope: &LoopScopeShape, - condition: &ASTNode, - loop_var_name: &str, -) -> Result { - let loop_cond_scope = LoopConditionScopeBox::analyze(loop_var_name, &[condition], Some(scope)); - - if loop_cond_scope.has_loop_body_local() { - let body_local_names = extract_body_local_names(&loop_cond_scope.vars); - return Err(format_unsupported_condition_error( - "pattern4", - &body_local_names, - )); - } - - Ok(loop_cond_scope) -} diff --git a/src/mir/join_ir/lowering/mod.rs b/src/mir/join_ir/lowering/mod.rs index e31bd3c7..c2f27a66 100644 --- a/src/mir/join_ir/lowering/mod.rs +++ b/src/mir/join_ir/lowering/mod.rs @@ -64,9 +64,7 @@ pub mod loop_update_analyzer; // Phase 197: Update expression analyzer for carri pub mod loop_update_summary; // Phase 170-C-2: Update pattern summary for shape detection pub(crate) mod loop_view_builder; // Phase 33-23: Loop lowering dispatch pub mod loop_with_break_minimal; // Phase 188-Impl-2: Pattern 2 minimal lowerer -pub mod loop_with_continue_minimal; pub(crate) mod return_collector; // Phase 284 P1: Return statement collector SSOT -pub(crate) mod return_jump_emitter; // Phase 284 P1: Return jump emission helper (Pattern4/5 reuse) pub mod method_call_lowerer; // Phase 224-B: MethodCall lowering (metadata-driven) pub mod user_method_policy; // Phase 252: User-defined method policy (SSOT for static box method whitelists) pub mod method_return_hint; // Phase 83: P3-D 既知メソッド戻り値型推論箱 @@ -77,7 +75,6 @@ pub(crate) mod step_schedule; // Phase 47-A: Generic step scheduler for P2/P3 (r // Phase 242-EX-A: loop_with_if_phi_minimal removed - replaced by loop_with_if_phi_if_sum pub mod loop_with_if_phi_if_sum; // Phase 213: Pattern 3 AST-based if-sum lowerer (Phase 242-EX-A: supports complex conditions) pub mod min_loop; -pub mod nested_loop_minimal; // Phase 188.3 P1: Pattern 6 nested loop minimal lowerer pub mod simple_while_minimal; // Phase 188-Impl-1: Pattern 1 minimal lowerer pub mod scan_with_init_minimal; // Phase 254 P1: Pattern 6 minimal lowerer (index_of/find/contains) pub mod scan_with_init_reverse; // Phase 257 P0: Pattern 6 reverse scan lowerer (last_index_of) diff --git a/src/mir/join_ir/lowering/nested_loop_minimal.rs b/src/mir/join_ir/lowering/nested_loop_minimal.rs deleted file mode 100644 index daa39394..00000000 --- a/src/mir/join_ir/lowering/nested_loop_minimal.rs +++ /dev/null @@ -1,397 +0,0 @@ -//! Phase 188.3 P1: Pattern 6 (Nested Loop Minimal) JoinIR Lowerer -//! -//! Target: apps/tests/phase1883_nested_minimal.hako -//! -//! Code: -//! ```nyash -//! static box Main { -//! main() { -//! local sum = 0 -//! local i = 0 -//! loop(i < 3) { -//! local j = 0 -//! loop(j < 3) { -//! sum = sum + 1 -//! j = j + 1 -//! } -//! i = i + 1 -//! } -//! return sum -//! } -//! } -//! ``` -//! -//! Expected JoinIR (4 functions): -//! ```text -//! fn main(i0, sum0): -//! Call(loop_step, [i0, sum0]) -//! Ret 0 -//! -//! fn loop_step(i, sum): // outer loop -//! exit_cond = !(i < 3) -//! Jump(k_exit, [sum], cond=exit_cond) -//! j0 = 0 -//! Call(inner_step, [j0, i, sum]) -//! -//! fn inner_step(j, i_outer, sum): // inner loop (tail recursion) -//! exit_cond = !(j < 3) -//! Jump(k_inner_exit, [i_outer, sum], cond=exit_cond) -//! sum_next = sum + 1 -//! j_next = j + 1 -//! Call(inner_step, [j_next, i_outer, sum_next]) -//! -//! fn k_inner_exit(i, sum): // outer continuation (after inner loop) -//! i_next = i + 1 -//! Call(loop_step, [i_next, sum]) -//! -//! fn k_exit(sum): -//! Ret sum -//! ``` -//! -//! ## Design Notes (Phase 188.3) -//! -//! - **4-function model**: main, loop_step (outer), inner_step, k_inner_exit, k_exit -//! - **Carrier design**: sum is passed as argument (not global) -//! - **Merge control**: continuation_func_ids includes k_exit, k_inner_exit, inner_step -//! - **Pattern1-based**: Both outer and inner are Pattern1 (no break/continue) - -use crate::mir::join_ir::lowering::canonical_names as cn; -use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace; -use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; -use crate::mir::join_ir::{ - BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst, - UnaryOp, -}; - -/// Canonical names for Pattern 6 continuations -pub const INNER_STEP: &str = "inner_step"; -pub const K_INNER_EXIT: &str = "k_inner_exit"; - -/// Lower Pattern 6 (Nested Loop Minimal) to JoinIR -/// -/// # Phase 188.3 P1: Minimal nested loop implementation -/// -/// This version generates JoinIR using **JoinValueSpace** for unified ValueId allocation. -/// It uses the Local region (1000+) to avoid collision with Param region (100-999). -/// -/// ## 4-Function Model -/// -/// - **main(i0, sum0)**: Entry point, calls loop_step -/// - **loop_step(i, sum)**: Outer loop header, initializes & calls inner loop -/// - **inner_step(j, i_outer, sum)**: Inner loop body (tail recursion) -/// - **k_inner_exit(i, sum)**: Outer continuation after inner loop exits -/// - **k_exit(sum)**: Final exit continuation -/// -/// # Arguments -/// -/// * `_scope` - LoopScopeShape (reserved for future generic implementation) -/// * `join_value_space` - Unified ValueId allocator (Phase 202-A) -/// -/// # Returns -/// -/// * `Some(JoinModule)` - Successfully lowered to JoinIR -/// * `None` - Pattern not matched (fallback to other lowerers) -/// -/// # Boundary Contract -/// -/// This function returns a JoinModule with: -/// - **Input slots**: main() params (i0, sum0) for outer loop variables -/// - **Caller responsibility**: Create JoinInlineBoundary to map param ValueIds to host variables -pub(crate) fn lower_nested_loop_minimal( - _scope: LoopScopeShape, - join_value_space: &mut JoinValueSpace, -) -> Option { - let mut join_module = JoinModule::new(); - - // ================================================================== - // Function IDs allocation - // ================================================================== - let main_id = JoinFuncId::new(0); - let loop_step_id = JoinFuncId::new(1); - let inner_step_id = JoinFuncId::new(2); - let k_inner_exit_id = JoinFuncId::new(3); - let k_exit_id = JoinFuncId::new(4); - - // ================================================================== - // ValueId allocation - main() function - // ================================================================== - let i_main_param = join_value_space.alloc_param(); // i0 (outer loop var) - let sum_main_param = join_value_space.alloc_param(); // sum0 (carrier) - let loop_result = join_value_space.alloc_local(); // result from loop_step - let const_0_main = join_value_space.alloc_local(); // return value - - // ================================================================== - // ValueId allocation - loop_step (outer loop) - // ================================================================== - let i_step_param = join_value_space.alloc_param(); // outer loop var - let sum_step_param = join_value_space.alloc_param(); // sum carrier - let const_3 = join_value_space.alloc_local(); // outer limit (3) - let cmp_i_lt = join_value_space.alloc_local(); // i < 3 - let exit_cond_outer = join_value_space.alloc_local(); // !(i < 3) - let j0 = join_value_space.alloc_local(); // inner loop init (0) - - // ================================================================== - // ValueId allocation - inner_step (inner loop) - // ================================================================== - let j_inner_param = join_value_space.alloc_param(); // inner loop var - let i_inner_param = join_value_space.alloc_param(); // outer var (read-only) - let sum_inner_param = join_value_space.alloc_param(); // sum carrier - let const_3_inner = join_value_space.alloc_local(); // inner limit (3) - let cmp_j_lt = join_value_space.alloc_local(); // j < 3 - let exit_cond_inner = join_value_space.alloc_local(); // !(j < 3) - let const_1_sum = join_value_space.alloc_local(); // increment (1) - let sum_next = join_value_space.alloc_local(); // sum + 1 - let const_1_j = join_value_space.alloc_local(); // increment (1) - let j_next = join_value_space.alloc_local(); // j + 1 - - // ================================================================== - // ValueId allocation - k_inner_exit (outer continuation) - // ================================================================== - let i_kexit_param = join_value_space.alloc_param(); // outer var - let sum_kexit_param = join_value_space.alloc_param(); // sum carrier - let const_1_i = join_value_space.alloc_local(); // increment (1) - let i_next = join_value_space.alloc_local(); // i + 1 - - // ================================================================== - // ValueId allocation - k_exit (final exit) - // ================================================================== - let sum_exit_param = join_value_space.alloc_param(); // sum for return - - // ================================================================== - // main() function - // ================================================================== - let mut main_func = - JoinFunction::new(main_id, cn::MAIN.to_string(), vec![i_main_param, sum_main_param]); - - // result = loop_step(i_main_param, sum_main_param) - main_func.body.push(JoinInst::Call { - func: loop_step_id, - args: vec![i_main_param, sum_main_param], - k_next: None, - dst: Some(loop_result), - }); - - // return 0 (statement position) - main_func.body.push(JoinInst::Compute(MirLikeInst::Const { - dst: const_0_main, - value: ConstValue::Integer(0), - })); - - main_func.body.push(JoinInst::Ret { - value: Some(const_0_main), - }); - - join_module.add_function(main_func); - - // ================================================================== - // loop_step(i, sum) - outer loop header - // ================================================================== - let mut loop_step_func = JoinFunction::new( - loop_step_id, - cn::LOOP_STEP.to_string(), - vec![i_step_param, sum_step_param], - ); - - // exit_cond = !(i < 3) - // Step 1: const 3 - loop_step_func - .body - .push(JoinInst::Compute(MirLikeInst::Const { - dst: const_3, - value: ConstValue::Integer(3), - })); - - // Step 2: cmp_i_lt = (i < 3) - loop_step_func - .body - .push(JoinInst::Compute(MirLikeInst::Compare { - dst: cmp_i_lt, - op: CompareOp::Lt, - lhs: i_step_param, - rhs: const_3, - })); - - // Step 3: exit_cond_outer = !cmp_i_lt - loop_step_func - .body - .push(JoinInst::Compute(MirLikeInst::UnaryOp { - dst: exit_cond_outer, - op: UnaryOp::Not, - operand: cmp_i_lt, - })); - - // Jump(k_exit, [sum], cond=exit_cond_outer) - loop_step_func.body.push(JoinInst::Jump { - cont: k_exit_id.as_cont(), - args: vec![sum_step_param], - cond: Some(exit_cond_outer), - }); - - // j0 = 0 (inner loop initialization) - loop_step_func - .body - .push(JoinInst::Compute(MirLikeInst::Const { - dst: j0, - value: ConstValue::Integer(0), - })); - - // Call(inner_step, [j0, i, sum]) - no continuation (tail call) - loop_step_func.body.push(JoinInst::Call { - func: inner_step_id, - args: vec![j0, i_step_param, sum_step_param], - k_next: None, - dst: None, - }); - - join_module.add_function(loop_step_func); - - // ================================================================== - // inner_step(j, i_outer, sum) - inner loop (tail recursion) - // ================================================================== - let mut inner_step_func = JoinFunction::new( - inner_step_id, - INNER_STEP.to_string(), - vec![j_inner_param, i_inner_param, sum_inner_param], - ); - - // exit_cond = !(j < 3) - // Step 1: const 3 - inner_step_func - .body - .push(JoinInst::Compute(MirLikeInst::Const { - dst: const_3_inner, - value: ConstValue::Integer(3), - })); - - // Step 2: cmp_j_lt = (j < 3) - inner_step_func - .body - .push(JoinInst::Compute(MirLikeInst::Compare { - dst: cmp_j_lt, - op: CompareOp::Lt, - lhs: j_inner_param, - rhs: const_3_inner, - })); - - // Step 3: exit_cond_inner = !cmp_j_lt - inner_step_func - .body - .push(JoinInst::Compute(MirLikeInst::UnaryOp { - dst: exit_cond_inner, - op: UnaryOp::Not, - operand: cmp_j_lt, - })); - - // Jump(k_inner_exit, [i_outer, sum], cond=exit_cond_inner) - inner_step_func.body.push(JoinInst::Jump { - cont: k_inner_exit_id.as_cont(), - args: vec![i_inner_param, sum_inner_param], - cond: Some(exit_cond_inner), - }); - - // sum_next = sum + 1 - // Step 1: const 1 - inner_step_func - .body - .push(JoinInst::Compute(MirLikeInst::Const { - dst: const_1_sum, - value: ConstValue::Integer(1), - })); - - // Step 2: sum_next = sum + 1 - inner_step_func - .body - .push(JoinInst::Compute(MirLikeInst::BinOp { - dst: sum_next, - op: BinOpKind::Add, - lhs: sum_inner_param, - rhs: const_1_sum, - })); - - // j_next = j + 1 - // Step 1: const 1 - inner_step_func - .body - .push(JoinInst::Compute(MirLikeInst::Const { - dst: const_1_j, - value: ConstValue::Integer(1), - })); - - // Step 2: j_next = j + 1 - inner_step_func - .body - .push(JoinInst::Compute(MirLikeInst::BinOp { - dst: j_next, - op: BinOpKind::Add, - lhs: j_inner_param, - rhs: const_1_j, - })); - - // Call(inner_step, [j_next, i_outer, sum_next]) - tail recursion - inner_step_func.body.push(JoinInst::Call { - func: inner_step_id, - args: vec![j_next, i_inner_param, sum_next], - k_next: None, - dst: None, - }); - - join_module.add_function(inner_step_func); - - // ================================================================== - // k_inner_exit(i, sum) - outer continuation (after inner loop) - // ================================================================== - let mut k_inner_exit_func = JoinFunction::new( - k_inner_exit_id, - K_INNER_EXIT.to_string(), - vec![i_kexit_param, sum_kexit_param], - ); - - // i_next = i + 1 - // Step 1: const 1 - k_inner_exit_func - .body - .push(JoinInst::Compute(MirLikeInst::Const { - dst: const_1_i, - value: ConstValue::Integer(1), - })); - - // Step 2: i_next = i + 1 - k_inner_exit_func - .body - .push(JoinInst::Compute(MirLikeInst::BinOp { - dst: i_next, - op: BinOpKind::Add, - lhs: i_kexit_param, - rhs: const_1_i, - })); - - // Call(loop_step, [i_next, sum]) - tail call to outer loop - k_inner_exit_func.body.push(JoinInst::Call { - func: loop_step_id, - args: vec![i_next, sum_kexit_param], - k_next: None, - dst: None, - }); - - join_module.add_function(k_inner_exit_func); - - // ================================================================== - // k_exit(sum) - final exit continuation - // ================================================================== - let mut k_exit_func = JoinFunction::new(k_exit_id, cn::K_EXIT.to_string(), vec![sum_exit_param]); - - // return sum - k_exit_func.body.push(JoinInst::Ret { - value: Some(sum_exit_param), - }); - - join_module.add_function(k_exit_func); - - // Set entry point - join_module.entry = Some(main_id); - - eprintln!("[joinir/pattern6] Generated JoinIR for Nested Loop Minimal Pattern"); - eprintln!("[joinir/pattern6] Functions: main, loop_step, inner_step, k_inner_exit, k_exit"); - - Some(join_module) -} diff --git a/src/mir/join_ir/lowering/return_collector.rs b/src/mir/join_ir/lowering/return_collector.rs index 5e5e0a48..d6b70fb5 100644 --- a/src/mir/join_ir/lowering/return_collector.rs +++ b/src/mir/join_ir/lowering/return_collector.rs @@ -9,11 +9,6 @@ //! - Return value must be integer literal (other types → Err) //! - Return can be in top-level if's then/else (recursive scan) //! - Return in nested loop → Err -//! -//! # Design -//! -//! This collector extracts return info from AST, which is later converted -//! to JoinInst::Ret in the JoinIR lowerer for proper MIR generation. use crate::ast::{ASTNode, LiteralValue}; @@ -22,11 +17,6 @@ use crate::ast::{ASTNode, LiteralValue}; pub struct ReturnInfo { /// The integer literal value (P1 scope: only integer literals supported) pub value: i64, - /// The if condition (Some if return is inside an if block) - /// This is used to generate conditional jump in JoinIR - pub condition: Option>, - /// Whether the return is in the else branch (negates condition) - pub in_else: bool, } /// Collect return statement from loop body @@ -40,13 +30,13 @@ pub struct ReturnInfo { /// /// # Returns /// -/// - `Ok(Some(info))` - Single return found with integer literal value and condition +/// - `Ok(Some(info))` - Single return found with integer literal value /// - `Ok(None)` - No return found /// - `Err(msg)` - Unsupported pattern (Fail-Fast) pub fn collect_return_from_body(body: &[ASTNode]) -> Result, String> { let mut found_returns: Vec = Vec::new(); - collect_returns_recursive(body, &mut found_returns, None, false)?; + collect_returns_recursive(body, &mut found_returns)?; match found_returns.len() { 0 => Ok(None), @@ -59,18 +49,9 @@ pub fn collect_return_from_body(body: &[ASTNode]) -> Result, } /// Recursive helper to collect return statements from if branches -/// -/// # Arguments -/// -/// * `body` - Current body to scan -/// * `found` - Accumulator for found return statements -/// * `current_condition` - If condition if we're inside an if block -/// * `in_else` - Whether we're in the else branch fn collect_returns_recursive( body: &[ASTNode], found: &mut Vec, - current_condition: Option<&ASTNode>, - in_else: bool, ) -> Result<(), String> { for stmt in body { match stmt { @@ -97,24 +78,17 @@ fn collect_returns_recursive( } }; - found.push(ReturnInfo { - value: return_value, - condition: current_condition.map(|c| Box::new(c.clone())), - in_else, - }); + found.push(ReturnInfo { value: return_value }); } ASTNode::If { - condition, then_body, else_body, .. } => { - // Recurse into if branches with the condition tracked - // For then branch: use condition as-is - collect_returns_recursive(then_body, found, Some(condition.as_ref()), false)?; - // For else branch: mark in_else=true (condition should be negated) + // Recurse into if branches + collect_returns_recursive(then_body, found)?; if let Some(else_b) = else_body { - collect_returns_recursive(else_b, found, Some(condition.as_ref()), true)?; + collect_returns_recursive(else_b, found)?; } } ASTNode::Loop { body: nested, .. } => { @@ -198,97 +172,42 @@ mod tests { #[test] fn test_no_return() { let body = vec![]; - let result = collect_return_from_body(&body); - assert!(result.is_ok()); - assert!(result.unwrap().is_none()); + let result = collect_return_from_body(&body).unwrap(); + assert!(result.is_none()); } #[test] - fn test_single_top_level_return() { + fn test_single_return() { let body = vec![make_int_return(7)]; - let result = collect_return_from_body(&body); - assert!(result.is_ok()); - let info = result.unwrap().unwrap(); - assert_eq!(info.value, 7); - assert!(info.condition.is_none()); // Top-level, no condition - assert!(!info.in_else); + let result = collect_return_from_body(&body).unwrap().unwrap(); + assert_eq!(result.value, 7); } #[test] - fn test_return_in_if_then() { - let body = vec![make_if_with_return(42)]; - let result = collect_return_from_body(&body); - assert!(result.is_ok()); - let info = result.unwrap().unwrap(); - assert_eq!(info.value, 42); - assert!(info.condition.is_some()); // Has condition from if - assert!(!info.in_else); // In then branch + fn test_return_in_if() { + let body = vec![make_if_with_return(5)]; + let result = collect_return_from_body(&body).unwrap().unwrap(); + assert_eq!(result.value, 5); } #[test] - fn test_return_in_if_else() { - let body = vec![ASTNode::If { - condition: Box::new(ASTNode::Variable { - name: "x".to_string(), - span: Span::unknown(), - }), - then_body: vec![], - else_body: Some(vec![make_int_return(99)]), - span: Span::unknown(), - }]; - let result = collect_return_from_body(&body); - assert!(result.is_ok()); - let info = result.unwrap().unwrap(); - assert_eq!(info.value, 99); - assert!(info.condition.is_some()); // Has condition from if - assert!(info.in_else); // In else branch (condition should be negated) - } - - #[test] - fn test_multiple_returns_err() { + fn test_multiple_returns_error() { let body = vec![make_int_return(1), make_int_return(2)]; let result = collect_return_from_body(&body); assert!(result.is_err()); - assert!(result.unwrap_err().contains("multiple return")); } #[test] - fn test_return_in_nested_loop_err() { + fn test_return_in_nested_loop_error() { let body = vec![ASTNode::Loop { condition: Box::new(ASTNode::Literal { - value: LiteralValue::Bool(true), + value: LiteralValue::Boolean(true), span: Span::unknown(), }), - body: vec![make_int_return(7)], + body: vec![make_int_return(3)], span: Span::unknown(), }]; let result = collect_return_from_body(&body); assert!(result.is_err()); - assert!(result.unwrap_err().contains("nested loop")); - } - - #[test] - fn test_non_integer_return_err() { - let body = vec![ASTNode::Return { - value: Some(Box::new(ASTNode::Literal { - value: LiteralValue::String("hello".to_string()), - span: Span::unknown(), - })), - span: Span::unknown(), - }]; - let result = collect_return_from_body(&body); - assert!(result.is_err()); - assert!(result.unwrap_err().contains("integer literal")); - } - - #[test] - fn test_void_return_err() { - let body = vec![ASTNode::Return { - value: None, - span: Span::unknown(), - }]; - let result = collect_return_from_body(&body); - assert!(result.is_err()); - assert!(result.unwrap_err().contains("void return")); } } diff --git a/src/mir/join_ir/lowering/return_jump_emitter.rs b/src/mir/join_ir/lowering/return_jump_emitter.rs deleted file mode 100644 index c5f097e8..00000000 --- a/src/mir/join_ir/lowering/return_jump_emitter.rs +++ /dev/null @@ -1,354 +0,0 @@ -//! Return Jump Emitter -//! -//! Phase 284 P1: Extracted from loop_with_continue_minimal.rs (lines 430-530) -//! -//! This module provides a reusable helper for emitting conditional Jump to k_return -//! in JoinIR loop patterns (Pattern4/5 and future patterns). -//! -//! ## Design Rationale -//! -//! Pattern4/5 both need to handle early return statements in loop bodies: -//! ```nyash -//! loop(i < 10) { -//! i = i + 1 -//! if (i == 3) { -//! return 7 // Early return -//! } -//! // ... loop body continues -//! } -//! ``` -//! -//! The JoinIR representation emits a conditional Jump to k_return: -//! ```text -//! return_cond = (i_next == 3) -//! Jump(k_return, [], cond=return_cond) -//! ``` -//! -//! This helper consolidates the code generation logic into a single SSOT, -//! preventing duplication across multiple pattern lowerers. -//! -//! ## Usage -//! -//! ```rust,ignore -//! use crate::mir::join_ir::lowering::return_jump_emitter::emit_return_conditional_jump; -//! -//! if let Some(ret_info) = return_info { -//! emit_return_conditional_jump( -//! &mut loop_step_func, -//! &Some(ret_info), -//! k_return_id, -//! &mut alloc_value, -//! &debug, -//! )?; -//! } -//! ``` - -use crate::ast::ASTNode; -use crate::mir::join_ir::lowering::debug_output_box::DebugOutputBox; -use crate::mir::join_ir::lowering::return_collector::ReturnInfo; -use crate::mir::join_ir::{CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, MirLikeInst}; -use crate::mir::ValueId; - -/// Emit conditional Jump to k_return -/// -/// Generates JoinIR instructions for early return handling in loop bodies. -/// -/// # Arguments -/// -/// * `loop_step_func` - The loop step function (body) to append instructions to -/// * `return_info` - Return statement metadata (value, condition, in_else flag) -/// * `k_return_id` - The k_return continuation function ID -/// * `loop_var_id` - ValueId of the loop variable to check (typically i_next after increment) -/// * `loop_var_name` - Name of the loop variable (for condition validation) -/// * `alloc_value` - Value ID allocator closure -/// * `debug` - Debug output box for logging -/// -/// # Returns -/// -/// * `Ok(())` - Instructions emitted successfully -/// * `Err(msg)` - Unsupported return pattern (Fail-Fast) -/// -/// # Phase 284 P1 Scope -/// -/// - Single return statement only -/// - Return condition: Simple comparison (loop_var == N) -/// - Return value: Integer literal -/// - Supports then/else branches (via `in_else` flag) -/// -/// # Implementation Notes -/// -/// The generated instructions follow this pattern: -/// -/// **Unconditional return:** -/// ```text -/// Jump(k_return, []) -/// ``` -/// -/// **Conditional return (then branch):** -/// ```text -/// const_cmp = Const(3) -/// return_cond = Compare(Eq, i_next, const_cmp) -/// Jump(k_return, [], cond=return_cond) -/// ``` -/// -/// **Conditional return (else branch):** -/// ```text -/// const_cmp = Const(3) -/// return_cond = Compare(Ne, i_next, const_cmp) // Note: Ne (negated) -/// Jump(k_return, [], cond=return_cond) -/// ``` -/// -/// # Future Extensions (Pattern4/5 reuse) -/// -/// This function is designed to be reusable across: -/// - Pattern4 (loop with continue) -/// - Pattern5 (infinite loop with early exit) -/// - Future patterns with early return support -/// -/// When extending, consider: -/// - Complex condition support (beyond loop_var == N) -/// - Multiple return statements (Phase 284 P2+) -/// - Return value expressions (beyond integer literals) -pub fn emit_return_conditional_jump( - loop_step_func: &mut JoinFunction, - return_info: Option<&ReturnInfo>, - k_return_id: JoinFuncId, - loop_var_id: ValueId, - loop_var_name: &str, - alloc_value: &mut dyn FnMut() -> ValueId, - debug: &DebugOutputBox, -) -> Result<(), String> { - let Some(ret_info) = return_info else { - // No return statement - nothing to emit - return Ok(()); - }; - - debug.log( - "phase284", - &format!( - "Generating return handling: value={}, has_condition={}", - ret_info.value, - ret_info.condition.is_some() - ), - ); - - if let Some(ref cond_ast) = ret_info.condition { - // Return is inside an if block - need to evaluate condition - // Phase 284 P1: Only support simple comparison (loop_var == N) - // Extract the comparison value from the condition AST - if let ASTNode::BinaryOp { - operator: crate::ast::BinaryOperator::Equal, - left, - right, - .. - } = cond_ast.as_ref() - { - // Check if left is variable and right is integer - let compare_value = match (left.as_ref(), right.as_ref()) { - ( - ASTNode::Variable { name, .. }, - ASTNode::Literal { - value: crate::ast::LiteralValue::Integer(n), - .. - }, - ) if name == loop_var_name => Some(*n), - _ => None, - }; - - if let Some(cmp_val) = compare_value { - // Generate: return_cond = (i_next == cmp_val) - let return_cmp_const = alloc_value(); - let return_cond = alloc_value(); - - loop_step_func - .body - .push(JoinInst::Compute(MirLikeInst::Const { - dst: return_cmp_const, - value: ConstValue::Integer(cmp_val), - })); - - // Phase 284 P1: Use loop_var_id (i_next) because fixture has increment BEFORE the if check - // Execution: i_next = i_param + 1, then check if i_next == compare_value - loop_step_func - .body - .push(JoinInst::Compute(MirLikeInst::Compare { - dst: return_cond, - op: if ret_info.in_else { - CompareOp::Ne - } else { - CompareOp::Eq - }, - lhs: loop_var_id, // Use loop_var_id (post-increment value) - rhs: return_cmp_const, - })); - - // Generate: Jump(k_return, [], cond=return_cond) - loop_step_func.body.push(JoinInst::Jump { - cont: k_return_id.as_cont(), - args: vec![], - cond: Some(return_cond), - }); - - debug.log( - "phase284", - &format!("Return condition: {} == {} → jump to k_return", loop_var_name, cmp_val), - ); - Ok(()) - } else { - debug.log( - "phase284", - "Return condition not supported (not loop_var == N pattern)", - ); - Err(format!( - "Phase 284 P1 scope: return condition variable must match loop variable '{}' (got: {:?})", - loop_var_name, left - )) - } - } else { - Err( - "Phase 284 P1 scope: return condition must be Equal comparison".to_string(), - ) - } - } else { - // Unconditional return - always jump to k_return - loop_step_func.body.push(JoinInst::Jump { - cont: k_return_id.as_cont(), - args: vec![], - cond: None, - }); - debug.log("phase284", "Unconditional return → jump to k_return"); - Ok(()) - } -} - -// ============================================================================ -// Unit Tests -// ============================================================================ - -#[cfg(test)] -mod tests { - use super::*; - use crate::ast::Span; - - fn make_debug() -> DebugOutputBox { - DebugOutputBox::new_dev("test") - } - - #[test] - fn test_unconditional_return() { - let mut func = JoinFunction::new(JoinFuncId::new(0), "test".to_string(), vec![]); - let info = ReturnInfo { - value: 42, - condition: None, - in_else: false, - }; - let return_info = Some(&info); - let k_return_id = JoinFuncId(99); - let loop_var_id = ValueId(100); - let mut counter = 0u32; - let mut alloc = || { - counter += 1; - ValueId(1000 + counter) - }; - let debug = make_debug(); - - let result = emit_return_conditional_jump( - &mut func, - return_info, - k_return_id, - loop_var_id, - "i", - &mut alloc, - &debug, - ); - - assert!(result.is_ok()); - assert_eq!(func.body.len(), 1); - match &func.body[0] { - JoinInst::Jump { cont, args, cond } => { - assert_eq!(*cont, k_return_id.as_cont()); - assert_eq!(args.len(), 0); - assert!(cond.is_none()); - } - _ => panic!("Expected Jump instruction"), - } - } - - #[test] - fn test_no_return() { - let mut func = JoinFunction::new(JoinFuncId::new(0), "test".to_string(), vec![]); - let return_info: Option<&ReturnInfo> = None; - let k_return_id = JoinFuncId(99); - let loop_var_id = ValueId(100); - let mut counter = 0u32; - let mut alloc = || { - counter += 1; - ValueId(1000 + counter) - }; - let debug = make_debug(); - - let result = emit_return_conditional_jump( - &mut func, - return_info, - k_return_id, - loop_var_id, - "i", - &mut alloc, - &debug, - ); - - assert!(result.is_ok()); - assert_eq!(func.body.len(), 0); // No instructions emitted - } - - #[test] - fn test_conditional_return_success() { - // Phase 284 P1: Test successful conditional return generation - let mut func = JoinFunction::new(JoinFuncId::new(0), "test".to_string(), vec![]); - let condition = Box::new(ASTNode::BinaryOp { - operator: crate::ast::BinaryOperator::Equal, - left: Box::new(ASTNode::Variable { - name: "i".to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: crate::ast::LiteralValue::Integer(3), - span: Span::unknown(), - }), - span: Span::unknown(), - }); - let info = ReturnInfo { - value: 7, - condition: Some(condition), - in_else: false, - }; - let return_info = Some(&info); - let k_return_id = JoinFuncId(99); - let loop_var_id = ValueId(100); // i_next - let mut counter = 0u32; - let mut alloc = || { - counter += 1; - ValueId(1000 + counter) - }; - let debug = make_debug(); - - let result = emit_return_conditional_jump( - &mut func, - return_info, - k_return_id, - loop_var_id, - "i", - &mut alloc, - &debug, - ); - - // Should succeed - assert!(result.is_ok(), "Failed: {:?}", result.err()); - - // Check generated instructions - // 1. Const(3) - // 2. Compare(Eq, loop_var_id, const) - // 3. Jump(k_return, [], cond) - assert_eq!(func.body.len(), 3); - } -}