diff --git a/src/mir/join_ir/lowering/generic_case_a.rs b/src/mir/join_ir/lowering/generic_case_a.rs index d430ef98..74febca6 100644 --- a/src/mir/join_ir/lowering/generic_case_a.rs +++ b/src/mir/join_ir/lowering/generic_case_a.rs @@ -6,10 +6,9 @@ //! - pinned/carrier/exit は LoopVarClassBox / LoopExitLivenessBox から渡された前提で扱う //! - 解析に失敗したら必ず None を返し、呼び元にフォールバックさせる -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeMap; -use crate::mir::join_ir::lowering::exit_args_resolver::resolve_exit_args; -use crate::mir::join_ir::lowering::loop_form_intake::{intake_loop_form, LoopFormIntake}; +use crate::mir::join_ir::lowering::loop_scope_shape::CaseAContext; use crate::mir::join_ir::lowering::value_id_ranges; use crate::mir::join_ir::lowering::value_id_ranges::skip_ws as vid; use crate::mir::join_ir::lowering::value_id_ranges::stage1_using_resolver as stage1_vid; @@ -33,13 +32,8 @@ pub fn lower_case_a_loop_to_joinir_for_minimal_skip_ws( query: &impl MirQuery, mir_func: &MirFunction, ) -> Option { - // 1) LoopForm が Case A っぽいか最低限チェック(header→body/exit、latch→header) - if loop_form.header == loop_form.exit { - eprintln!("[joinir/generic_case_a] loop_form malformed (header == exit), fallback"); - return None; - } + // 追加の latch チェック(skip_ws 固有) if loop_form.latch != loop_form.body && loop_form.latch != loop_form.header { - // minimal_skip_ws では latch==body or header 想定。違えばフォールバック。 eprintln!( "[joinir/generic_case_a] unexpected latch {:?} (body={:?}, header={:?}), fallback", loop_form.latch, loop_form.body, loop_form.header @@ -47,53 +41,25 @@ pub fn lower_case_a_loop_to_joinir_for_minimal_skip_ws( return None; } - // 2) MIR から pinned/carrier/exit 情報を抽出する - let LoopFormIntake { - pinned_ordered: ordered_pinned, - carrier_ordered: ordered_carriers, - name_to_header_id: _name_to_id, - header_snapshot: header_vals_mir, - exit_snapshots, - exit_preds: _exit_preds, - } = intake_loop_form(loop_form, var_classes, query, mir_func)?; + // CaseAContext で共通ロジックを実行 + let ctx = CaseAContext::new( + loop_form, + var_classes, + exit_live, + query, + mir_func, + "skip_ws", + |offset| vid::loop_step(offset), + )?; - // JoinIR 用の ValueId を範囲から割り当て(名前→ID マップ) - let mut name_to_loop_id: BTreeMap = BTreeMap::new(); - let mut offset = 0; - for name in &ordered_pinned { - name_to_loop_id.insert(name.clone(), vid::loop_step(offset)); - offset += 1; - } - for name in &ordered_carriers { - name_to_loop_id.insert(name.clone(), vid::loop_step(offset)); - offset += 1; - } + // JoinModule を手書き skip_ws と同じ形で構築(ValueId 範囲も揃える) + let string_key = ctx.pinned_name_or_first(0)?; + let len_key = ctx.pinned_name_or_first(1).unwrap_or_else(|| string_key.clone()); + let index_key = ctx.carrier_name_or_first(0)?; - let pinned_ids: Vec = ordered_pinned - .iter() - .filter_map(|k| name_to_loop_id.get(k).copied()) - .collect(); - let carrier_ids: Vec = ordered_carriers - .iter() - .filter_map(|k| name_to_loop_id.get(k).copied()) - .collect(); - - // Exit live 集合(LoopExitLivenessBox を利用) - let exit_live_set: BTreeSet = - exit_live.compute_live_at_exit(query, loop_form.exit, &header_vals_mir, &exit_snapshots); - let exit_args = resolve_exit_args(&exit_live_set, &name_to_loop_id, &ordered_carriers)?; - - // 3) JoinModule を手書き skip_ws と同じ形で構築(ValueId 範囲も揃える) - let string_key = ordered_pinned.get(0).cloned()?; - let len_key = ordered_pinned - .get(1) - .cloned() - .unwrap_or_else(|| string_key.clone()); - let index_key = ordered_carriers.get(0).cloned()?; - - let s_loop = *name_to_loop_id.get(&string_key)?; - let i_loop = *name_to_loop_id.get(&index_key)?; - let n_loop = *name_to_loop_id.get(&len_key)?; + let s_loop = ctx.get_loop_id(&string_key)?; + let i_loop = ctx.get_loop_id(&index_key)?; + let n_loop = ctx.get_loop_id(&len_key)?; let mut join_module = JoinModule::new(); @@ -123,9 +89,10 @@ pub fn lower_case_a_loop_to_joinir_for_minimal_skip_ws( })); let loop_step_id = JoinFuncId::new(1); - let loop_call_args: Vec = ordered_pinned + let loop_call_args: Vec = ctx + .ordered_pinned .iter() - .chain(ordered_carriers.iter()) + .chain(ctx.ordered_carriers.iter()) .map(|name| entry_name_to_id.get(name).copied()) .collect::>()?; @@ -140,7 +107,7 @@ pub fn lower_case_a_loop_to_joinir_for_minimal_skip_ws( join_module.add_function(skip_func); // loop_step(s, i, n) - let header_shape = LoopHeaderShape::new_manual(pinned_ids.clone(), carrier_ids.clone()); + let header_shape = LoopHeaderShape::new_manual(ctx.pinned_ids.clone(), ctx.carrier_ids.clone()); let loop_params = header_shape.to_loop_step_params(); let mut loop_step_func = JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_params.clone()); @@ -164,10 +131,10 @@ pub fn lower_case_a_loop_to_joinir_for_minimal_skip_ws( rhs: n_loop, })); - let _exit_shape = if exit_args.is_empty() { + let _exit_shape = if ctx.exit_args.is_empty() { LoopExitShape::new_manual(vec![i_loop]) } else { - LoopExitShape::new_manual(exit_args.clone()) + LoopExitShape::new_manual(ctx.exit_args.clone()) }; // exit_args = [i] が期待値 // if i >= n { return i } @@ -279,60 +246,24 @@ pub fn lower_case_a_loop_to_joinir_for_trim_minimal( query: &impl MirQuery, mir_func: &MirFunction, ) -> Option { - if loop_form.header == loop_form.exit { - eprintln!("[joinir/generic_case_a/trim] loop_form malformed (header == exit), fallback"); - return None; - } + // CaseAContext で共通ロジックを実行 + let ctx = CaseAContext::new( + loop_form, + var_classes, + exit_live, + query, + mir_func, + "trim", + |offset| value_id_ranges::funcscanner_trim::loop_step(offset), + )?; - let LoopFormIntake { - pinned_ordered: ordered_pinned, - carrier_ordered: ordered_carriers, - name_to_header_id: _name_to_id, - header_snapshot, - exit_snapshots, - exit_preds: _exit_preds, - } = intake_loop_form(loop_form, var_classes, query, mir_func)?; + let string_key = ctx.pinned_name_or_first(0)?; + let base_key = ctx.pinned_name_or_first(1).unwrap_or_else(|| string_key.clone()); + let carrier_key = ctx.carrier_name_or_first(0)?; - let mut name_to_loop_id: BTreeMap = BTreeMap::new(); - let mut offset = 0; - for name in &ordered_pinned { - name_to_loop_id.insert( - name.clone(), - value_id_ranges::funcscanner_trim::loop_step(offset), - ); - offset += 1; - } - for name in &ordered_carriers { - name_to_loop_id.insert( - name.clone(), - value_id_ranges::funcscanner_trim::loop_step(offset), - ); - offset += 1; - } - - let pinned_ids: Vec = ordered_pinned - .iter() - .filter_map(|k| name_to_loop_id.get(k).copied()) - .collect(); - let carrier_ids: Vec = ordered_carriers - .iter() - .filter_map(|k| name_to_loop_id.get(k).copied()) - .collect(); - - let exit_live_set: BTreeSet = - exit_live.compute_live_at_exit(query, loop_form.exit, &header_snapshot, &exit_snapshots); - let exit_args = resolve_exit_args(&exit_live_set, &name_to_loop_id, &ordered_carriers)?; - - let string_key = ordered_pinned.get(0).cloned()?; - let base_key = ordered_pinned - .get(1) - .cloned() - .unwrap_or_else(|| string_key.clone()); - let carrier_key = ordered_carriers.get(0).cloned()?; - - let s_loop = *name_to_loop_id.get(&string_key)?; - let b_loop = *name_to_loop_id.get(&base_key)?; - let e_loop = *name_to_loop_id.get(&carrier_key)?; + let s_loop = ctx.get_loop_id(&string_key)?; + let b_loop = ctx.get_loop_id(&base_key)?; + let e_loop = ctx.get_loop_id(&carrier_key)?; let mut join_module = JoinModule::new(); @@ -400,9 +331,10 @@ pub fn lower_case_a_loop_to_joinir_for_trim_minimal( entry_name_to_id.insert(base_key.clone(), b_val); entry_name_to_id.insert(carrier_key.clone(), e_init); - let loop_call_args: Vec = ordered_pinned + let loop_call_args: Vec = ctx + .ordered_pinned .iter() - .chain(ordered_carriers.iter()) + .chain(ctx.ordered_carriers.iter()) .map(|name| entry_name_to_id.get(name).copied()) .collect::>()?; @@ -418,7 +350,7 @@ pub fn lower_case_a_loop_to_joinir_for_trim_minimal( join_module.add_function(trim_main_func); // loop_step(str, b, e) - let header_shape = LoopHeaderShape::new_manual(pinned_ids.clone(), carrier_ids.clone()); + let header_shape = LoopHeaderShape::new_manual(ctx.pinned_ids.clone(), ctx.carrier_ids.clone()); let loop_params = header_shape.to_loop_step_params(); let mut loop_step_func = JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_params.clone()); @@ -461,10 +393,10 @@ pub fn lower_case_a_loop_to_joinir_for_trim_minimal( op: CompareOp::Eq, })); - let _exit_shape_trim = if exit_args.is_empty() { + let _exit_shape_trim = if ctx.exit_args.is_empty() { LoopExitShape::new_manual(vec![e_loop]) } else { - LoopExitShape::new_manual(exit_args.clone()) + LoopExitShape::new_manual(ctx.exit_args.clone()) }; loop_step_func.body.push(JoinInst::Jump { @@ -794,67 +726,26 @@ pub fn lower_case_a_loop_to_joinir_for_append_defs_minimal( query: &impl MirQuery, mir_func: &MirFunction, ) -> Option { - if loop_form.header == loop_form.exit { - eprintln!( - "[joinir/generic_case_a/append_defs] loop_form malformed (header == exit), fallback" - ); - return None; - } + // CaseAContext で共通ロジックを実行 + let ctx = CaseAContext::new( + loop_form, + var_classes, + exit_live, + query, + mir_func, + "append_defs", + |offset| value_id_ranges::funcscanner_append_defs::loop_step(offset), + )?; - let LoopFormIntake { - pinned_ordered: ordered_pinned, - carrier_ordered: ordered_carriers, - name_to_header_id: _name_to_id, - header_snapshot, - exit_snapshots, - exit_preds: _exit_preds, - } = intake_loop_form(loop_form, var_classes, query, mir_func)?; + let dst_key = ctx.pinned_name_or_first(0)?; + let defs_key = ctx.pinned_name_or_first(1).unwrap_or_else(|| dst_key.clone()); + let n_key = ctx.pinned_name_or_first(2).unwrap_or_else(|| defs_key.clone()); + let i_key = ctx.carrier_name_or_first(0)?; - let mut name_to_loop_id: BTreeMap = BTreeMap::new(); - let mut offset = 0; - for name in &ordered_pinned { - name_to_loop_id.insert( - name.clone(), - value_id_ranges::funcscanner_append_defs::loop_step(offset), - ); - offset += 1; - } - for name in &ordered_carriers { - name_to_loop_id.insert( - name.clone(), - value_id_ranges::funcscanner_append_defs::loop_step(offset), - ); - offset += 1; - } - - let pinned_ids: Vec = ordered_pinned - .iter() - .filter_map(|k| name_to_loop_id.get(k).copied()) - .collect(); - let carrier_ids: Vec = ordered_carriers - .iter() - .filter_map(|k| name_to_loop_id.get(k).copied()) - .collect(); - - let exit_live_set: BTreeSet = - exit_live.compute_live_at_exit(query, loop_form.exit, &header_snapshot, &exit_snapshots); - let exit_args = resolve_exit_args(&exit_live_set, &name_to_loop_id, &ordered_carriers)?; - - let dst_key = ordered_pinned.get(0).cloned()?; - let defs_key = ordered_pinned - .get(1) - .cloned() - .unwrap_or_else(|| dst_key.clone()); - let n_key = ordered_pinned - .get(2) - .cloned() - .unwrap_or_else(|| defs_key.clone()); - let i_key = ordered_carriers.get(0).cloned()?; - - let dst_loop = *name_to_loop_id.get(&dst_key)?; - let defs_loop = *name_to_loop_id.get(&defs_key)?; - let n_loop = *name_to_loop_id.get(&n_key)?; - let i_loop = *name_to_loop_id.get(&i_key)?; + let dst_loop = ctx.get_loop_id(&dst_key)?; + let defs_loop = ctx.get_loop_id(&defs_key)?; + let n_loop = ctx.get_loop_id(&n_key)?; + let i_loop = ctx.get_loop_id(&i_key)?; let mut join_module = JoinModule::new(); @@ -882,9 +773,10 @@ pub fn lower_case_a_loop_to_joinir_for_append_defs_minimal( entry_name_to_id.insert("n".to_string(), n_param); entry_name_to_id.insert("i".to_string(), i_init); - let loop_call_args: Vec = ordered_pinned + let loop_call_args: Vec = ctx + .ordered_pinned .iter() - .chain(ordered_carriers.iter()) + .chain(ctx.ordered_carriers.iter()) .map(|name| entry_name_to_id.get(name).copied()) .collect::>()?; @@ -900,7 +792,7 @@ pub fn lower_case_a_loop_to_joinir_for_append_defs_minimal( join_module.add_function(entry_func); // loop_step(dst, defs_box, n, i) - let header_shape = LoopHeaderShape::new_manual(pinned_ids.clone(), carrier_ids.clone()); + let header_shape = LoopHeaderShape::new_manual(ctx.pinned_ids.clone(), ctx.carrier_ids.clone()); let loop_params = header_shape.to_loop_step_params(); let mut loop_step_func = JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_params.clone()); @@ -915,11 +807,11 @@ pub fn lower_case_a_loop_to_joinir_for_append_defs_minimal( rhs: n_loop, })); - let _exit_shape = LoopExitShape::new_manual(exit_args.clone()); + let _exit_shape = LoopExitShape::new_manual(ctx.exit_args.clone()); loop_step_func.body.push(JoinInst::Jump { cont: JoinContId::new(0), - args: exit_args.clone(), + args: ctx.exit_args.clone(), cond: Some(cmp_result), }); @@ -988,69 +880,30 @@ pub fn lower_case_a_loop_to_joinir_for_stage1_usingresolver_minimal( query: &impl MirQuery, mir_func: &MirFunction, ) -> Option { - if loop_form.header == loop_form.exit { - eprintln!("[joinir/generic_case_a/stage1] loop_form malformed (header == exit), fallback"); - return None; - } + // CaseAContext で共通ロジックを実行 + let ctx = CaseAContext::new( + loop_form, + var_classes, + exit_live, + query, + mir_func, + "stage1", + |offset| stage1_vid::loop_step(offset), + )?; - let LoopFormIntake { - pinned_ordered: ordered_pinned, - carrier_ordered: ordered_carriers, - name_to_header_id: _name_to_id, - header_snapshot, - exit_snapshots, - exit_preds: _exit_preds, - } = intake_loop_form(loop_form, var_classes, query, mir_func)?; + let entries_key = ctx.pinned_name_or_first(0)?; + let n_key = ctx.pinned_name_or_first(1).unwrap_or_else(|| entries_key.clone()); + let modules_key = ctx.pinned_name_or_first(2).unwrap_or_else(|| entries_key.clone()); + let seen_key = ctx.pinned_name_or_first(3).unwrap_or_else(|| entries_key.clone()); + let prefix_key = ctx.carrier_name_or_first(0)?; + let i_key = ctx.carrier_name_or_first(1).unwrap_or_else(|| prefix_key.clone()); - let mut name_to_loop_id: BTreeMap = BTreeMap::new(); - let mut offset = 0; - for name in &ordered_pinned { - name_to_loop_id.insert(name.clone(), stage1_vid::loop_step(offset)); - offset += 1; - } - for name in &ordered_carriers { - name_to_loop_id.insert(name.clone(), stage1_vid::loop_step(offset)); - offset += 1; - } - - let pinned_ids: Vec = ordered_pinned - .iter() - .filter_map(|k| name_to_loop_id.get(k).copied()) - .collect(); - let carrier_ids: Vec = ordered_carriers - .iter() - .filter_map(|k| name_to_loop_id.get(k).copied()) - .collect(); - - let exit_live_set: BTreeSet = - exit_live.compute_live_at_exit(query, loop_form.exit, &header_snapshot, &exit_snapshots); - let exit_args = resolve_exit_args(&exit_live_set, &name_to_loop_id, &ordered_carriers)?; - - let entries_key = ordered_pinned.get(0).cloned()?; - let n_key = ordered_pinned - .get(1) - .cloned() - .unwrap_or_else(|| entries_key.clone()); - let modules_key = ordered_pinned - .get(2) - .cloned() - .unwrap_or_else(|| entries_key.clone()); - let seen_key = ordered_pinned - .get(3) - .cloned() - .unwrap_or_else(|| entries_key.clone()); - let prefix_key = ordered_carriers.get(0).cloned()?; - let i_key = ordered_carriers - .get(1) - .cloned() - .unwrap_or_else(|| prefix_key.clone()); - - let entries_loop = *name_to_loop_id.get(&entries_key)?; - let n_loop = *name_to_loop_id.get(&n_key)?; - let modules_loop = *name_to_loop_id.get(&modules_key)?; - let seen_loop = *name_to_loop_id.get(&seen_key)?; - let prefix_loop = *name_to_loop_id.get(&prefix_key)?; - let i_loop = *name_to_loop_id.get(&i_key)?; + let entries_loop = ctx.get_loop_id(&entries_key)?; + let n_loop = ctx.get_loop_id(&n_key)?; + let modules_loop = ctx.get_loop_id(&modules_key)?; + let seen_loop = ctx.get_loop_id(&seen_key)?; + let prefix_loop = ctx.get_loop_id(&prefix_key)?; + let i_loop = ctx.get_loop_id(&i_key)?; let mut join_module = JoinModule::new(); @@ -1089,9 +942,10 @@ pub fn lower_case_a_loop_to_joinir_for_stage1_usingresolver_minimal( entry_name_to_id.insert(prefix_key.clone(), prefix_param); entry_name_to_id.insert(i_key.clone(), i_init); - let loop_call_args: Vec = ordered_pinned + let loop_call_args: Vec = ctx + .ordered_pinned .iter() - .chain(ordered_carriers.iter()) + .chain(ctx.ordered_carriers.iter()) .map(|name| entry_name_to_id.get(name).copied()) .collect::>()?; @@ -1107,7 +961,7 @@ pub fn lower_case_a_loop_to_joinir_for_stage1_usingresolver_minimal( join_module.add_function(resolve_func); // loop_step(entries, n, modules, seen, prefix, i) - let header_shape = LoopHeaderShape::new_manual(pinned_ids.clone(), carrier_ids.clone()); + let header_shape = LoopHeaderShape::new_manual(ctx.pinned_ids.clone(), ctx.carrier_ids.clone()); let loop_params = header_shape.to_loop_step_params(); let mut loop_step_func = JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_params.clone()); @@ -1122,10 +976,10 @@ pub fn lower_case_a_loop_to_joinir_for_stage1_usingresolver_minimal( rhs: n_loop, })); - let exit_shape = if exit_args.is_empty() { + let exit_shape = if ctx.exit_args.is_empty() { LoopExitShape::new_manual(vec![prefix_loop]) } else { - LoopExitShape::new_manual(exit_args.clone()) + LoopExitShape::new_manual(ctx.exit_args.clone()) }; loop_step_func.body.push(JoinInst::Jump { diff --git a/src/mir/join_ir/lowering/loop_form_intake.rs b/src/mir/join_ir/lowering/loop_form_intake.rs index b49a37e2..d0cefc77 100644 --- a/src/mir/join_ir/lowering/loop_form_intake.rs +++ b/src/mir/join_ir/lowering/loop_form_intake.rs @@ -14,7 +14,6 @@ use std::collections::{BTreeMap, BTreeSet}; pub(crate) struct LoopFormIntake { pub pinned_ordered: Vec, pub carrier_ordered: Vec, - pub name_to_header_id: BTreeMap, pub header_snapshot: BTreeMap, pub exit_snapshots: Vec<(BasicBlockId, BTreeMap)>, pub exit_preds: Vec, @@ -204,7 +203,6 @@ pub(crate) fn intake_loop_form( Some(LoopFormIntake { pinned_ordered: ordered_pinned, carrier_ordered: ordered_carriers, - name_to_header_id: header_vals_mir.clone(), header_snapshot: header_vals_mir, exit_snapshots, exit_preds, diff --git a/src/mir/join_ir/lowering/loop_scope_shape.rs b/src/mir/join_ir/lowering/loop_scope_shape.rs new file mode 100644 index 00000000..bcb512c5 --- /dev/null +++ b/src/mir/join_ir/lowering/loop_scope_shape.rs @@ -0,0 +1,499 @@ +//! LoopScopeShape - ループ変数スコープの統合ビュー +//! +//! # Purpose +//! +//! 既存の3箱(LoopVarClassBox, LoopExitLivenessBox, LocalScopeInspectorBox)の +//! 情報を1つの構造体に統合し、JoinIR lowering が参照する唯一のソースにする。 +//! +//! # Design Philosophy (Box-First) +//! +//! Phase 29/30 で以下の責務分離を実現: +//! - LoopForm: ループの「形」(preheader/header/body/latch/exit) +//! - LoopScopeShape: 変数の「役割」(pinned/carrier/body_local/exit_live) +//! - JoinIR: 関数と継続で表現された制御構造 +//! +//! # Phase 29 Strategy +//! +//! 現在は「既存箱を呼ぶだけの薄いラッパー」として実装。 +//! 将来は既存箱を吸収して LoopScopeShape を唯一のソースにする(Phase 30)。 +//! +//! # Usage +//! +//! ```ignore +//! let scope = LoopScopeShape::from_existing_boxes( +//! &loop_form_intake, +//! &var_classes, +//! &exit_live, +//! query, +//! )?; +//! +//! // JoinIR lowering では scope のフィールドを参照 +//! for name in &scope.pinned { +//! // ... +//! } +//! ``` + +use std::collections::{BTreeMap, BTreeSet}; + +use crate::mir::join_ir::lowering::exit_args_resolver::resolve_exit_args; +use crate::mir::join_ir::lowering::loop_form_intake::{intake_loop_form, LoopFormIntake}; +use crate::mir::loop_form::LoopForm; +use crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox; +use crate::mir::phi_core::loop_var_classifier::{LoopVarClass, LoopVarClassBox}; +use crate::mir::{BasicBlockId, MirFunction, MirQuery, ValueId}; + +/// ループ変数スコープの統合ビュー +/// +/// # Fields +/// +/// - `pinned`: ループ外から来て変わらない変数(header/exit PHI 両方必要) +/// - `carriers`: 各イテレーションで更新される変数(header/exit PHI 両方必要) +/// - `body_locals`: ループ内だけで完結する変数(JoinIR では参照しない) +/// - `exit_live`: ループ後に使われる変数(needs_exit_phi() == true) +/// - `progress_carrier`: ループを前に進める変数(将来の Verifier 用) +#[derive(Debug, Clone)] +pub(crate) struct LoopScopeShape { + /// Loop-crossing parameters (always need header/exit PHI) + pub pinned: BTreeSet, + + /// Loop-modified variables (always need header/exit PHI) + pub carriers: BTreeSet, + + /// Body-local variables (JoinIR doesn't need these) + /// Includes both BodyLocalExit and BodyLocalInternal + pub body_locals: BTreeSet, + + /// Variables live at exit (needs_exit_phi() == true) + /// = pinned ∪ carriers ∪ BodyLocalExit + pub exit_live: BTreeSet, + + /// Progress carrier for loop termination check (future Verifier use) + /// Typically the loop index variable (i, pos, etc.) + pub progress_carrier: Option, +} + +impl LoopScopeShape { + /// Create LoopScopeShape from existing boxes (Phase 29: thin wrapper) + /// + /// # Arguments + /// + /// - `intake`: LoopFormIntake containing classified variable info + /// - `var_classes`: LoopVarClassBox for classification + /// - `exit_live_box`: LoopExitLivenessBox for exit liveness + /// - `query`: MirQuery for liveness computation + /// - `exit_block`: Exit block ID + /// + /// # Returns + /// + /// Some(LoopScopeShape) if successful, None if critical data is missing. + /// + /// # Phase 29 Behavior + /// + /// Currently just reads from existing boxes without modifying behavior. + /// Future Phase 30 will absorb the boxes entirely. + pub(crate) fn from_existing_boxes( + intake: &LoopFormIntake, + var_classes: &LoopVarClassBox, + exit_live_box: &LoopExitLivenessBox, + query: &impl MirQuery, + exit_block: BasicBlockId, + ) -> Option { + // Extract pinned and carriers from intake (already classified) + let pinned: BTreeSet = intake.pinned_ordered.iter().cloned().collect(); + let carriers: BTreeSet = intake.carrier_ordered.iter().cloned().collect(); + + // Build LocalScopeInspectorBox for body_local classification + let mut inspector = + crate::mir::phi_core::local_scope_inspector::LocalScopeInspectorBox::new(); + inspector.record_snapshot(exit_block, &intake.header_snapshot); + for (bb, snap) in &intake.exit_snapshots { + inspector.record_snapshot(*bb, snap); + } + + // Classify all variables to find body_locals + let all_names: Vec = intake.header_snapshot.keys().cloned().collect(); + let classified = var_classes.classify_all( + &all_names, + &intake.pinned_ordered, + &intake.carrier_ordered, + &inspector, + &intake.exit_preds, + ); + + let mut body_locals: BTreeSet = BTreeSet::new(); + let mut exit_live: BTreeSet = BTreeSet::new(); + + for (name, class) in &classified { + match class { + LoopVarClass::Pinned => { + exit_live.insert(name.clone()); + } + LoopVarClass::Carrier => { + exit_live.insert(name.clone()); + } + LoopVarClass::BodyLocalExit => { + body_locals.insert(name.clone()); + exit_live.insert(name.clone()); + } + LoopVarClass::BodyLocalInternal => { + body_locals.insert(name.clone()); + // NOT in exit_live (Option C: skip exit PHI) + } + } + } + + // Compute exit_live from LoopExitLivenessBox (Phase 1: usually empty) + let exit_live_from_box = exit_live_box.compute_live_at_exit( + query, + exit_block, + &intake.header_snapshot, + &intake.exit_snapshots, + ); + + // Merge: exit_live = (classification-based) ∪ (liveness-box-based) + for name in exit_live_from_box { + exit_live.insert(name); + } + + // Determine progress_carrier (heuristic: first carrier, typically 'i') + let progress_carrier = carriers.iter().next().cloned(); + + Some(Self { + pinned, + carriers, + body_locals, + exit_live, + progress_carrier, + }) + } + + /// Check if a variable needs header PHI + pub fn needs_header_phi(&self, var_name: &str) -> bool { + self.pinned.contains(var_name) || self.carriers.contains(var_name) + } + + /// Check if a variable needs exit PHI + pub fn needs_exit_phi(&self, var_name: &str) -> bool { + self.exit_live.contains(var_name) + } + + /// Get ordered pinned variables (for JoinIR parameter generation) + pub fn pinned_ordered(&self) -> Vec { + self.pinned.iter().cloned().collect() + } + + /// Get ordered carrier variables (for JoinIR parameter generation) + pub fn carriers_ordered(&self) -> Vec { + self.carriers.iter().cloned().collect() + } + + /// Get all variables that need header PHI (pinned + carriers) + pub fn header_phi_vars(&self) -> Vec { + let mut result: Vec = self.pinned.iter().cloned().collect(); + result.extend(self.carriers.iter().cloned()); + result + } + + /// Get all variables that need exit PHI + pub fn exit_phi_vars(&self) -> Vec { + self.exit_live.iter().cloned().collect() + } +} + +// ============================================================================ +// CaseAContext - generic_case_a 共通ロジックの集約 +// ============================================================================ + +/// Case A lowering の共通コンテキスト +/// +/// generic_case_a.rs の4関数に共通するロジックを集約し、 +/// 約200行の重複コードを削減する。 +/// +/// # Usage +/// +/// ```ignore +/// let ctx = CaseAContext::new( +/// loop_form, var_classes, exit_live, query, mir_func, +/// "skip_ws", +/// |offset| vid::loop_step(offset), +/// )?; +/// +/// // ctx から必要な情報を取り出して JoinModule を構築 +/// let header_shape = LoopHeaderShape::new_manual(ctx.pinned_ids.clone(), ctx.carrier_ids.clone()); +/// ``` +#[derive(Debug, Clone)] +pub(crate) struct CaseAContext { + /// LoopScopeShape(変数スコープ情報) + pub scope: LoopScopeShape, + + /// 順序付き pinned 変数名 + pub ordered_pinned: Vec, + + /// 順序付き carrier 変数名 + pub ordered_carriers: Vec, + + /// 変数名 → ループ関数内 ValueId のマッピング + pub name_to_loop_id: BTreeMap, + + /// pinned 変数の ValueId 列 + pub pinned_ids: Vec, + + /// carrier 変数の ValueId 列 + pub carrier_ids: Vec, + + /// exit 時に渡す引数の ValueId 列 + pub exit_args: Vec, +} + +impl CaseAContext { + /// CaseAContext を構築する + /// + /// # Arguments + /// + /// - `loop_form`: ループ構造情報 + /// - `var_classes`: 変数分類器 + /// - `exit_live`: exit liveness 情報 + /// - `query`: MIR クエリ + /// - `mir_func`: MIR 関数 + /// - `log_tag`: ログ出力用タグ(例: "skip_ws", "trim") + /// - `loop_step_id_fn`: offset から ValueId を生成する関数 + /// + /// # Returns + /// + /// Some(CaseAContext) if successful, None if validation fails or data is missing. + pub(crate) fn new( + loop_form: &LoopForm, + var_classes: &LoopVarClassBox, + exit_live: &LoopExitLivenessBox, + query: &impl MirQuery, + mir_func: &MirFunction, + log_tag: &str, + loop_step_id_fn: F, + ) -> Option + where + F: Fn(u32) -> ValueId, + { + // 1) LoopForm validation + if loop_form.header == loop_form.exit { + eprintln!( + "[joinir/generic_case_a/{}] loop_form malformed (header == exit), fallback", + log_tag + ); + return None; + } + + // 2) MIR から pinned/carrier/exit 情報を抽出 + let intake = intake_loop_form(loop_form, var_classes, query, mir_func)?; + + // 3) LoopScopeShape を構築 + let scope = LoopScopeShape::from_existing_boxes( + &intake, + var_classes, + exit_live, + query, + loop_form.exit, + )?; + + let ordered_pinned = scope.pinned_ordered(); + let ordered_carriers = scope.carriers_ordered(); + + // 4) 変数名 → ValueId マッピングを構築 + let mut name_to_loop_id: BTreeMap = BTreeMap::new(); + let mut offset: u32 = 0; + for name in &ordered_pinned { + name_to_loop_id.insert(name.clone(), loop_step_id_fn(offset)); + offset += 1; + } + for name in &ordered_carriers { + name_to_loop_id.insert(name.clone(), loop_step_id_fn(offset)); + offset += 1; + } + + // 5) pinned_ids / carrier_ids を構築 + let pinned_ids: Vec = ordered_pinned + .iter() + .filter_map(|k| name_to_loop_id.get(k).copied()) + .collect(); + let carrier_ids: Vec = ordered_carriers + .iter() + .filter_map(|k| name_to_loop_id.get(k).copied()) + .collect(); + + // 6) exit_args を解決 + let exit_args = resolve_exit_args(&scope.exit_live, &name_to_loop_id, &ordered_carriers)?; + + Some(Self { + scope, + ordered_pinned, + ordered_carriers, + name_to_loop_id, + pinned_ids, + carrier_ids, + exit_args, + }) + } + + /// 変数名から loop 関数内の ValueId を取得 + pub fn get_loop_id(&self, name: &str) -> Option { + self.name_to_loop_id.get(name).copied() + } + + /// pinned 変数の n 番目の名前を取得(なければ 0 番目を使う) + pub fn pinned_name_or_first(&self, index: usize) -> Option { + self.ordered_pinned + .get(index) + .cloned() + .or_else(|| self.ordered_pinned.first().cloned()) + } + + /// carrier 変数の n 番目の名前を取得(なければ 0 番目を使う) + pub fn carrier_name_or_first(&self, index: usize) -> Option { + self.ordered_carriers + .get(index) + .cloned() + .or_else(|| self.ordered_carriers.first().cloned()) + } +} + +// ============================================================================ +// Unit Tests +// ============================================================================ + +#[cfg(test)] +mod tests { + use super::*; + use crate::mir::ValueId; + use std::collections::BTreeMap; + + fn make_dummy_intake() -> LoopFormIntake { + let mut header_snapshot = BTreeMap::new(); + header_snapshot.insert("s".to_string(), ValueId(10)); + header_snapshot.insert("n".to_string(), ValueId(20)); + header_snapshot.insert("i".to_string(), ValueId(30)); + + LoopFormIntake { + pinned_ordered: vec!["s".to_string(), "n".to_string()], + carrier_ordered: vec!["i".to_string()], + header_snapshot, + exit_snapshots: vec![], + exit_preds: vec![], + } + } + + struct EmptyQuery; + impl MirQuery for EmptyQuery { + fn insts_in_block(&self, _bb: BasicBlockId) -> &[crate::mir::MirInstruction] { + &[] + } + fn succs(&self, _bb: BasicBlockId) -> Vec { + Vec::new() + } + fn reads_of(&self, _inst: &crate::mir::MirInstruction) -> Vec { + Vec::new() + } + fn writes_of(&self, _inst: &crate::mir::MirInstruction) -> Vec { + Vec::new() + } + } + + #[test] + fn test_from_existing_boxes_basic() { + let intake = make_dummy_intake(); + let var_classes = LoopVarClassBox::new(); + let exit_live_box = LoopExitLivenessBox::new(); + let query = EmptyQuery; + + let scope = LoopScopeShape::from_existing_boxes( + &intake, + &var_classes, + &exit_live_box, + &query, + BasicBlockId::new(100), + ); + + assert!(scope.is_some()); + let scope = scope.unwrap(); + + // pinned should be {s, n} + assert!(scope.pinned.contains("s")); + assert!(scope.pinned.contains("n")); + assert_eq!(scope.pinned.len(), 2); + + // carriers should be {i} + assert!(scope.carriers.contains("i")); + assert_eq!(scope.carriers.len(), 1); + + // progress_carrier should be "i" + assert_eq!(scope.progress_carrier, Some("i".to_string())); + } + + #[test] + fn test_needs_header_phi() { + let intake = make_dummy_intake(); + let var_classes = LoopVarClassBox::new(); + let exit_live_box = LoopExitLivenessBox::new(); + let query = EmptyQuery; + + let scope = LoopScopeShape::from_existing_boxes( + &intake, + &var_classes, + &exit_live_box, + &query, + BasicBlockId::new(100), + ) + .unwrap(); + + assert!(scope.needs_header_phi("s")); + assert!(scope.needs_header_phi("n")); + assert!(scope.needs_header_phi("i")); + assert!(!scope.needs_header_phi("unknown")); + } + + #[test] + fn test_needs_exit_phi() { + let intake = make_dummy_intake(); + let var_classes = LoopVarClassBox::new(); + let exit_live_box = LoopExitLivenessBox::new(); + let query = EmptyQuery; + + let scope = LoopScopeShape::from_existing_boxes( + &intake, + &var_classes, + &exit_live_box, + &query, + BasicBlockId::new(100), + ) + .unwrap(); + + // Pinned and Carrier should need exit PHI + assert!(scope.needs_exit_phi("s")); + assert!(scope.needs_exit_phi("n")); + assert!(scope.needs_exit_phi("i")); + } + + #[test] + fn test_ordered_accessors() { + let intake = make_dummy_intake(); + let var_classes = LoopVarClassBox::new(); + let exit_live_box = LoopExitLivenessBox::new(); + let query = EmptyQuery; + + let scope = LoopScopeShape::from_existing_boxes( + &intake, + &var_classes, + &exit_live_box, + &query, + BasicBlockId::new(100), + ) + .unwrap(); + + let pinned = scope.pinned_ordered(); + assert_eq!(pinned.len(), 2); + assert!(pinned.contains(&"s".to_string())); + assert!(pinned.contains(&"n".to_string())); + + let carriers = scope.carriers_ordered(); + assert_eq!(carriers.len(), 1); + assert!(carriers.contains(&"i".to_string())); + } +} diff --git a/src/mir/join_ir/lowering/mod.rs b/src/mir/join_ir/lowering/mod.rs index 5d24d16d..97e5753a 100644 --- a/src/mir/join_ir/lowering/mod.rs +++ b/src/mir/join_ir/lowering/mod.rs @@ -19,6 +19,7 @@ pub mod funcscanner_append_defs; pub mod funcscanner_trim; pub mod generic_case_a; pub mod loop_form_intake; +pub mod loop_scope_shape; pub mod min_loop; pub mod skip_ws; pub mod stage1_using_resolver;