refactor(joinir): introduce CaseAContext to consolidate common lowering logic

- Add CaseAContext struct in loop_scope_shape.rs to centralize:
  - LoopForm validation
  - intake_loop_form invocation
  - LoopScopeShape construction
  - Variable name → ValueId mapping
  - pinned_ids/carrier_ids/exit_args resolution

- Refactor all 4 generic_case_a.rs functions to use CaseAContext:
  - lower_case_a_loop_to_joinir_for_minimal_skip_ws
  - lower_case_a_loop_to_joinir_for_trim_minimal
  - lower_case_a_loop_to_joinir_for_append_defs_minimal
  - lower_case_a_loop_to_joinir_for_stage1_usingresolver_minimal

- Remove unused name_to_header_id field from LoopFormIntake
  (was duplicate of header_snapshot)

Code reduction: ~200 lines of duplicated pattern → 4 lines

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-25 06:32:08 +09:00
parent 844e53d96c
commit 31e458e7fa
4 changed files with 600 additions and 248 deletions

View File

@ -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<JoinModule> {
// 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<String, ValueId> = 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<ValueId> = ordered_pinned
.iter()
.filter_map(|k| name_to_loop_id.get(k).copied())
.collect();
let carrier_ids: Vec<ValueId> = ordered_carriers
.iter()
.filter_map(|k| name_to_loop_id.get(k).copied())
.collect();
// Exit live 集合LoopExitLivenessBox を利用)
let exit_live_set: BTreeSet<String> =
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<ValueId> = ordered_pinned
let loop_call_args: Vec<ValueId> = ctx
.ordered_pinned
.iter()
.chain(ordered_carriers.iter())
.chain(ctx.ordered_carriers.iter())
.map(|name| entry_name_to_id.get(name).copied())
.collect::<Option<_>>()?;
@ -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<JoinModule> {
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<String, ValueId> = 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<ValueId> = ordered_pinned
.iter()
.filter_map(|k| name_to_loop_id.get(k).copied())
.collect();
let carrier_ids: Vec<ValueId> = ordered_carriers
.iter()
.filter_map(|k| name_to_loop_id.get(k).copied())
.collect();
let exit_live_set: BTreeSet<String> =
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<ValueId> = ordered_pinned
let loop_call_args: Vec<ValueId> = ctx
.ordered_pinned
.iter()
.chain(ordered_carriers.iter())
.chain(ctx.ordered_carriers.iter())
.map(|name| entry_name_to_id.get(name).copied())
.collect::<Option<_>>()?;
@ -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<JoinModule> {
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<String, ValueId> = 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<ValueId> = ordered_pinned
.iter()
.filter_map(|k| name_to_loop_id.get(k).copied())
.collect();
let carrier_ids: Vec<ValueId> = ordered_carriers
.iter()
.filter_map(|k| name_to_loop_id.get(k).copied())
.collect();
let exit_live_set: BTreeSet<String> =
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<ValueId> = ordered_pinned
let loop_call_args: Vec<ValueId> = ctx
.ordered_pinned
.iter()
.chain(ordered_carriers.iter())
.chain(ctx.ordered_carriers.iter())
.map(|name| entry_name_to_id.get(name).copied())
.collect::<Option<_>>()?;
@ -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<JoinModule> {
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<String, ValueId> = 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<ValueId> = ordered_pinned
.iter()
.filter_map(|k| name_to_loop_id.get(k).copied())
.collect();
let carrier_ids: Vec<ValueId> = ordered_carriers
.iter()
.filter_map(|k| name_to_loop_id.get(k).copied())
.collect();
let exit_live_set: BTreeSet<String> =
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<ValueId> = ordered_pinned
let loop_call_args: Vec<ValueId> = ctx
.ordered_pinned
.iter()
.chain(ordered_carriers.iter())
.chain(ctx.ordered_carriers.iter())
.map(|name| entry_name_to_id.get(name).copied())
.collect::<Option<_>>()?;
@ -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 {

View File

@ -14,7 +14,6 @@ use std::collections::{BTreeMap, BTreeSet};
pub(crate) struct LoopFormIntake {
pub pinned_ordered: Vec<String>,
pub carrier_ordered: Vec<String>,
pub name_to_header_id: BTreeMap<String, ValueId>,
pub header_snapshot: BTreeMap<String, ValueId>,
pub exit_snapshots: Vec<(BasicBlockId, BTreeMap<String, ValueId>)>,
pub exit_preds: Vec<BasicBlockId>,
@ -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,

View File

@ -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<String>,
/// Loop-modified variables (always need header/exit PHI)
pub carriers: BTreeSet<String>,
/// Body-local variables (JoinIR doesn't need these)
/// Includes both BodyLocalExit and BodyLocalInternal
pub body_locals: BTreeSet<String>,
/// Variables live at exit (needs_exit_phi() == true)
/// = pinned carriers BodyLocalExit
pub exit_live: BTreeSet<String>,
/// Progress carrier for loop termination check (future Verifier use)
/// Typically the loop index variable (i, pos, etc.)
pub progress_carrier: Option<String>,
}
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<Self> {
// Extract pinned and carriers from intake (already classified)
let pinned: BTreeSet<String> = intake.pinned_ordered.iter().cloned().collect();
let carriers: BTreeSet<String> = 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<String> = 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<String> = BTreeSet::new();
let mut exit_live: BTreeSet<String> = 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<String> {
self.pinned.iter().cloned().collect()
}
/// Get ordered carrier variables (for JoinIR parameter generation)
pub fn carriers_ordered(&self) -> Vec<String> {
self.carriers.iter().cloned().collect()
}
/// Get all variables that need header PHI (pinned + carriers)
pub fn header_phi_vars(&self) -> Vec<String> {
let mut result: Vec<String> = 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<String> {
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<String>,
/// 順序付き carrier 変数名
pub ordered_carriers: Vec<String>,
/// 変数名 → ループ関数内 ValueId のマッピング
pub name_to_loop_id: BTreeMap<String, ValueId>,
/// pinned 変数の ValueId 列
pub pinned_ids: Vec<ValueId>,
/// carrier 変数の ValueId 列
pub carrier_ids: Vec<ValueId>,
/// exit 時に渡す引数の ValueId 列
pub exit_args: Vec<ValueId>,
}
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<F>(
loop_form: &LoopForm,
var_classes: &LoopVarClassBox,
exit_live: &LoopExitLivenessBox,
query: &impl MirQuery,
mir_func: &MirFunction,
log_tag: &str,
loop_step_id_fn: F,
) -> Option<Self>
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<String, ValueId> = 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<ValueId> = ordered_pinned
.iter()
.filter_map(|k| name_to_loop_id.get(k).copied())
.collect();
let carrier_ids: Vec<ValueId> = 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<ValueId> {
self.name_to_loop_id.get(name).copied()
}
/// pinned 変数の n 番目の名前を取得(なければ 0 番目を使う)
pub fn pinned_name_or_first(&self, index: usize) -> Option<String> {
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<String> {
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<BasicBlockId> {
Vec::new()
}
fn reads_of(&self, _inst: &crate::mir::MirInstruction) -> Vec<ValueId> {
Vec::new()
}
fn writes_of(&self, _inst: &crate::mir::MirInstruction) -> Vec<ValueId> {
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()));
}
}

View File

@ -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;