phase29ap(p13): remove dead code after legacy removal
This commit is contained in:
@ -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)
|
||||
|
||||
|
||||
@ -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`
|
||||
|
||||
|
||||
@ -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(再発防止の土台)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 `<var> < <int_lit>` only
|
||||
fn extract_loop_condition_plan(cond: &ASTNode) -> Result<Option<(String, i64)>, String> {
|
||||
fn extract_loop_condition_plan(cond: &ASTNode) -> Result<Option<String>, 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<Option<(String, i64)>,
|
||||
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<Option<ASTNode>, Str
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Extract carrier updates: `<var> = <var> + <var>` 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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<String, UpdateExpr>,
|
||||
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<JoinInst>,
|
||||
) -> 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(())
|
||||
}
|
||||
@ -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<JoinInst>), 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::<Pattern2ScopeManager>::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))
|
||||
}
|
||||
@ -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<String, UpdateExpr>, // 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<String, crate::mir::BindingId>,
|
||||
>,
|
||||
) -> 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::<Vec<_>>()
|
||||
),
|
||||
);
|
||||
|
||||
// ==================================================================
|
||||
// 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<ValueId> = 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<ValueId> = 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<ValueId> = 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<ValueId> = Vec::new();
|
||||
let mut carrier_merged_ids: Vec<ValueId> = 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::<Vec<_>>()
|
||||
),
|
||||
);
|
||||
|
||||
// ==================================================================
|
||||
// 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))
|
||||
}
|
||||
@ -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<LoopConditionScope, String> {
|
||||
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)
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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<JoinModule> {
|
||||
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)
|
||||
}
|
||||
@ -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<Box<ASTNode>>,
|
||||
/// 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<Option<ReturnInfo>, String> {
|
||||
let mut found_returns: Vec<ReturnInfo> = 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<Option<ReturnInfo>,
|
||||
}
|
||||
|
||||
/// 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<ReturnInfo>,
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user