Remove Trio boxes and tidy loop scope warnings
This commit is contained in:
@ -10,7 +10,7 @@
|
||||
- **✅ JoinIR ラインは Phase 68 で一旦 Chapter Close!**
|
||||
- Phase 27-67 で JoinIR の「第1章(構造 + PHI + 型ヒント SSOT)」が完了。
|
||||
- 4つの柱(Structure / Scope / JoinIR / Type Hints)が確立。
|
||||
- 今後は Trio 削除ライン、wasm/Web デモライン、最適化ラインに分岐。
|
||||
- Trio 削除ライン(Phase 70 完了)を経て、wasm/Web デモラインと最適化ラインに分岐。
|
||||
- 詳細: [phase-30-final-joinir-world/README.md](docs/private/roadmap2/phases/phase-30-final-joinir-world/README.md)
|
||||
|
||||
- **最終ゴール**
|
||||
@ -19,9 +19,9 @@
|
||||
- 既存の PHI 箱(if_phi.rs / PhiBuilderBox / conservative.rs / Trio 等)は、JoinIR 側のカバレッジが十分になったところから順に削っていく。
|
||||
|
||||
- **これから(Phase 69+)**
|
||||
- Trio 削除ライン: LoopVarClassBox / LoopExitLivenessBox / LocalScopeInspectorBox を LoopScopeShape に完全吸収。
|
||||
- wasm/Web デモライン: JoinIR ベースの軽量デモ実装。
|
||||
- 最適化ライン: JoinIR の最適化パスと LLVM/ny-llvmc 統合。
|
||||
- Trio 削除ライン: 完了(Phase 70、LoopScopeShape SSOT)
|
||||
|
||||
---
|
||||
|
||||
@ -94,16 +94,15 @@
|
||||
- 69-2: `merge_exit_with_classification` から Inspector 引数を削除し、LoopScopeShape/ExitAnalysis 経由で必要な情報を取る形に縮退(約 42 行削減)。既存の 3 テストはすべて PASS。
|
||||
- 69-3: `BasicBlock.predecessors` を `HashSet` → `BTreeSet` に変更するなど、MIR の predecessor イテレーションを決定的にし、これまで非決定性でフラッキーだった 2 つのループ系テストを安定化。loopform 14/14 / merge_exit 3/3 を含む関連テストはすべて PASS。
|
||||
- 未了:
|
||||
- 69-4: Trio 3 箱(LoopVarClassBox / LoopExitLivenessBox / LocalScopeInspectorBox)の本体削除は、legacy bridge 完全移行後の Phase 67+ で扱う。
|
||||
- 69-5: conservative.rs の docs/ 移設も今後の小フェーズとして残しておく。
|
||||
- 追加完了 (Phase 70):
|
||||
- 69-4: Trio 3 箱(LoopVarClassBox / LoopExitLivenessBox / LocalScopeInspectorBox)を削除し、LoopScopeShape を SSOT とする構成に移行。
|
||||
|
||||
## 2. 次の一手(Phase 69+)
|
||||
|
||||
### 直近の候補タスク
|
||||
|
||||
- **Trio 削除ライン**(Phase 69 候補)
|
||||
- LoopVarClassBox / LoopExitLivenessBox / LocalScopeInspectorBox を LoopScopeShape に完全吸収
|
||||
- Phase 48-7: Trio/LoopScopeShape の将来扱い整理(docs-only)
|
||||
- **Phase 70 完了**: Trio 3 箱削除(LoopScopeShape SSOT)
|
||||
|
||||
- **P3-C 拡大 / If PHI 本体削除**(Phase 70 候補)
|
||||
- GenericTypeResolver 経由で全 P3-C ケースをカバー
|
||||
|
||||
@ -500,3 +500,31 @@ Phase 70 実装完了と判定する条件:
|
||||
**Phase 70 実装開始準備完了!** 🚀
|
||||
|
||||
**次のステップ**: Phase 70 実装(見積もり3時間、削減見込み ~1,443行)
|
||||
|
||||
---
|
||||
|
||||
## 🛠 Phase 70-1 / 70-2 実施メモ(2025-11-30)
|
||||
|
||||
### 70-1: loop_form_intake.rs Trio 使用削除 ✅
|
||||
|
||||
- 変更ファイル: `src/mir/join_ir/lowering/loop_form_intake.rs`
|
||||
- 内容:
|
||||
- LocalScopeInspectorBox / LoopVarClassBox を使った変数分類ロジックを完全削除。
|
||||
- `pinned_hint` / `carrier_hint` から `BTreeSet` ベースで `ordered_pinned` / `ordered_carriers` を作る薄い箱に縮退。
|
||||
- 実際の pinned/carrier 判定は `LoopScopeShape::from_loop_form()` 側に一本化(二重分類問題の解消)。
|
||||
- 行数: 29 行 → 2 行(約 27 行削減)。
|
||||
- テスト: loopform 14/14 PASS。
|
||||
|
||||
### 70-2: loop_to_join.rs 呼び出し側修正 ✅
|
||||
|
||||
- 変更ファイル: `src/mir/join_ir/lowering/loop_to_join.rs`
|
||||
- 内容:
|
||||
- `intake_loop_form(loop_form, &Default::default(), &query, func)` を `intake_loop_form(loop_form, &query, func)` に変更。
|
||||
- Trio のダミー引数を削除し、JoinIR lowering からの Trio 依存を 0 に。
|
||||
- テスト:
|
||||
- loopform テストは 70-1 と同じく 14/14 PASS。
|
||||
- `cargo test --release` 全体は既知の 39 失敗を含むが、新規エラーの追加はなし。
|
||||
|
||||
Phase 70-1/2 により、LoopToJoinLowerer 側からは完全に Trio が姿を消し、
|
||||
LoopScopeShape が pinned/carrier/exit_live の SSOT になった。
|
||||
Phase 70-3 以降では json_v0_bridge と phi_core 本体に残っている Trio を設計通りに畳んでいく。
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
//! 制約(必読):
|
||||
//! - 条件式の中身を解析しない(Compare/BinOp を MIR そのままコピーするだけ)
|
||||
//! - 多重ヘッダ/ネストループは対象外(v1 は minimal_ssa_skip_ws の単純ループ専用)
|
||||
//! - pinned/carrier/exit は LoopVarClassBox / LoopExitLivenessBox から渡された前提で扱う
|
||||
//! - pinned/carrier/exit は LoopScopeShape から渡された前提で扱う
|
||||
//! - 解析に失敗したら必ず None を返し、呼び元にフォールバックさせる
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
@ -4,9 +4,10 @@
|
||||
//! - LoopForm + MirQuery + MirFunction から、JoinIR 降下に必要な情報を抽出する。
|
||||
//! - pinned/carrier 推定と ValueId マッピング、header/exit スナップショット収集を一元化。
|
||||
//! - generic_case_a/B など複数ロワーで再利用するための「入口箱」だよ。
|
||||
//!
|
||||
//! Phase 70-1: Trio (LocalScopeInspectorBox, LoopVarClassBox) 依存削除
|
||||
//! - 変数分類は LoopScopeShape::from_loop_form() に一本化(二重分類問題解消)
|
||||
|
||||
use crate::mir::phi_core::local_scope_inspector::LocalScopeInspectorBox;
|
||||
use crate::mir::phi_core::loop_var_classifier::{LoopVarClass, LoopVarClassBox};
|
||||
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, MirQuery, ValueId};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
@ -21,10 +22,12 @@ pub(crate) struct LoopFormIntake {
|
||||
|
||||
/// LoopForm + MIR から pinned/carrier とスナップショットを抽出する。
|
||||
///
|
||||
/// Phase 70-1: var_classes 引数削除(Trio 依存排除)
|
||||
/// 実際の変数分類は呼び出し側が LoopScopeShape::from_loop_form() で実施
|
||||
///
|
||||
/// 失敗時は None(フォールバック用)。
|
||||
pub(crate) fn intake_loop_form(
|
||||
loop_form: &crate::mir::loop_form::LoopForm,
|
||||
var_classes: &LoopVarClassBox,
|
||||
query: &impl MirQuery,
|
||||
mir_func: &MirFunction,
|
||||
) -> Option<LoopFormIntake> {
|
||||
@ -165,36 +168,10 @@ pub(crate) fn intake_loop_form(
|
||||
}
|
||||
}
|
||||
|
||||
// LocalScopeInspector に snapshot を登録して VarClass 判定の土台を整える
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
inspector.record_snapshot(loop_form.header, &header_vals_mir);
|
||||
for (bb, snap) in &exit_snapshots {
|
||||
inspector.record_snapshot(*bb, snap);
|
||||
}
|
||||
|
||||
let all_names: Vec<String> = header_vals_mir.keys().cloned().collect();
|
||||
let classified = var_classes.classify_all(
|
||||
&all_names,
|
||||
&pinned_hint,
|
||||
&carrier_hint,
|
||||
&inspector,
|
||||
&exit_preds,
|
||||
);
|
||||
|
||||
let ordered_pinned: Vec<String> = classified
|
||||
.iter()
|
||||
.filter(|(_, c)| matches!(c, LoopVarClass::Pinned))
|
||||
.map(|(n, _)| n.clone())
|
||||
.collect::<BTreeSet<_>>()
|
||||
.into_iter()
|
||||
.collect();
|
||||
let ordered_carriers: Vec<String> = classified
|
||||
.iter()
|
||||
.filter(|(_, c)| matches!(c, LoopVarClass::Carrier))
|
||||
.map(|(n, _)| n.clone())
|
||||
.collect::<BTreeSet<_>>()
|
||||
.into_iter()
|
||||
.collect();
|
||||
// Phase 70-1: Trio 分類を削除し、pinned_hint/carrier_hint をそのまま返す
|
||||
// 実際の分類は LoopScopeShape::from_loop_form() 内部で実施される(二重分類問題解消)
|
||||
let ordered_pinned: Vec<String> = pinned_hint.into_iter().collect::<BTreeSet<_>>().into_iter().collect();
|
||||
let ordered_carriers: Vec<String> = carrier_hint.into_iter().collect::<BTreeSet<_>>().into_iter().collect();
|
||||
|
||||
if ordered_pinned.is_empty() || ordered_carriers.is_empty() {
|
||||
return None;
|
||||
|
||||
@ -3,94 +3,44 @@
|
||||
//! LoopForm / 既存箱から LoopScopeShape を構築し、Case-A minimal ターゲットは
|
||||
//! analyze_case_a パスにルーティングする。
|
||||
//!
|
||||
//! # Phase 48-6: Trio 依存の境界
|
||||
//!
|
||||
//! このファイルは **唯一 Trio(LoopVarClassBox/LoopExitLivenessBox/LocalScopeInspectorBox)を
|
||||
//! 知らないといけない層** として設計されている。
|
||||
//!
|
||||
//! ## Trio が表に出る箇所(意図的に許容)
|
||||
//!
|
||||
//! 1. **builder.rs**(このファイル): from_existing_boxes_legacy とその helper 関数
|
||||
//! 2. **phi_core/* 自身**: Trio の実装ファイル(保持)
|
||||
//! 3. **legacy bridge**: json_v0_bridge/lowering/loop_.rs(Phase 62+ で段階廃止)
|
||||
//! 4. **テスト**: loop_scope_shape/tests.rs, loop_form_intake.rs(検証用)
|
||||
//!
|
||||
//! ## 外部からの利用
|
||||
//!
|
||||
//! 外部(JoinIR lowering, LoopToJoinLowerer 等)は **LoopScopeShape の API のみ** を使用し、
|
||||
//! Trio の存在を知らない設計になっている(Phase 48-2/48-4 完了)。
|
||||
//! Trio legacy boxes は完全に除去済み。
|
||||
//! LoopForm / LoopFormIntake から LoopScopeShape を構築し、変数分類と定義位置を
|
||||
//! LoopScopeShape の内部に閉じ込める。
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use crate::mir::control_form::LoopId;
|
||||
use crate::mir::join_ir::lowering::loop_form_intake::LoopFormIntake;
|
||||
use crate::mir::loop_form::LoopForm;
|
||||
// Phase 48-6: Trio 依存(builder.rs のみが知る層)
|
||||
use crate::mir::phi_core::local_scope_inspector::LocalScopeInspectorBox;
|
||||
use crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox;
|
||||
use crate::mir::phi_core::loop_var_classifier::{LoopVarClass, LoopVarClassBox};
|
||||
use crate::mir::{BasicBlockId, MirQuery};
|
||||
|
||||
use super::case_a::{is_case_a_minimal_target, validate_case_a_structural};
|
||||
use super::shape::LoopScopeShape;
|
||||
use super::shape::{LoopScopeShape, LoopVarClass};
|
||||
|
||||
impl LoopScopeShape {
|
||||
/// Case-A ルーティング込みで LoopScopeShape を構築
|
||||
pub(crate) fn from_existing_boxes(
|
||||
pub(crate) fn from_loop_form(
|
||||
loop_form: &LoopForm,
|
||||
intake: &LoopFormIntake,
|
||||
var_classes: &LoopVarClassBox,
|
||||
exit_live_box: &LoopExitLivenessBox,
|
||||
query: &impl MirQuery,
|
||||
_query: &impl MirQuery,
|
||||
func_name: Option<&str>,
|
||||
) -> Option<Self> {
|
||||
if let Some(name) = func_name {
|
||||
if is_case_a_minimal_target(name) {
|
||||
return Self::analyze_case_a(
|
||||
loop_form,
|
||||
intake,
|
||||
var_classes,
|
||||
exit_live_box,
|
||||
query,
|
||||
name,
|
||||
);
|
||||
return Self::analyze_case_a(loop_form, intake, name);
|
||||
}
|
||||
}
|
||||
|
||||
Self::from_existing_boxes_legacy(loop_form, intake, var_classes, exit_live_box, query)
|
||||
}
|
||||
|
||||
/// Trio 引数なしで LoopScopeShape を構築(Trio 内部化)
|
||||
pub(crate) fn from_loop_form(
|
||||
loop_form: &LoopForm,
|
||||
intake: &LoopFormIntake,
|
||||
query: &impl MirQuery,
|
||||
func_name: Option<&str>,
|
||||
) -> Option<Self> {
|
||||
let var_classes = LoopVarClassBox::new();
|
||||
let exit_live_box = LoopExitLivenessBox::new();
|
||||
|
||||
Self::from_existing_boxes(
|
||||
loop_form,
|
||||
intake,
|
||||
&var_classes,
|
||||
&exit_live_box,
|
||||
query,
|
||||
func_name,
|
||||
)
|
||||
Self::build_from_intake(loop_form, intake)
|
||||
}
|
||||
|
||||
/// Case-A minimal 用の解析パス(Phase 48-5: 構造判定検証追加)
|
||||
fn analyze_case_a(
|
||||
loop_form: &LoopForm,
|
||||
intake: &LoopFormIntake,
|
||||
var_classes: &LoopVarClassBox,
|
||||
exit_live_box: &LoopExitLivenessBox,
|
||||
query: &impl MirQuery,
|
||||
func_name: &str,
|
||||
) -> Option<Self> {
|
||||
let result =
|
||||
Self::from_existing_boxes_legacy(loop_form, intake, var_classes, exit_live_box, query)?;
|
||||
let result = Self::build_from_intake(loop_form, intake)?;
|
||||
|
||||
// Phase 48-5: 構造判定検証(警告のみ、将来的に厳格化)
|
||||
validate_case_a_structural(loop_form, &result, func_name);
|
||||
@ -108,25 +58,9 @@ impl LoopScopeShape {
|
||||
Some(result)
|
||||
}
|
||||
|
||||
/// 既存箱ベースの従来実装(Case-A 以外のループで使用)
|
||||
///
|
||||
/// # Phase 48-6: Trio を知らないといけない唯一の層
|
||||
///
|
||||
/// この関数は **Trio(LoopVarClassBox/LoopExitLivenessBox/LocalScopeInspectorBox)を
|
||||
/// 直接受け取り、LoopScopeShape に変換する責務** を持つ。
|
||||
///
|
||||
/// - **外部からは呼ばれない**: from_loop_form() が Trio を内部生成して隠蔽(Phase 48-2)
|
||||
/// - **Legacy bridge のみ**: json_v0_bridge/lowering/loop_.rs が直接呼ぶ(Phase 62+ 廃止予定)
|
||||
/// - **helper 関数**: 下記3関数も Trio を使用(このファイル内でのみ可視)
|
||||
/// - build_inspector(): LocalScopeInspectorBox 構築
|
||||
/// - classify_body_and_exit(): LoopVarClassBox 使用
|
||||
/// - merge_exit_live_from_box(): LoopExitLivenessBox 使用
|
||||
fn from_existing_boxes_legacy(
|
||||
fn build_from_intake(
|
||||
loop_form: &LoopForm,
|
||||
intake: &LoopFormIntake,
|
||||
var_classes: &LoopVarClassBox,
|
||||
exit_live_box: &LoopExitLivenessBox,
|
||||
query: &impl MirQuery,
|
||||
) -> Option<Self> {
|
||||
let layout = block_layout(loop_form);
|
||||
|
||||
@ -145,18 +79,16 @@ impl LoopScopeShape {
|
||||
let pinned: BTreeSet<String> = intake.pinned_ordered.iter().cloned().collect();
|
||||
let carriers: BTreeSet<String> = intake.carrier_ordered.iter().cloned().collect();
|
||||
|
||||
let inspector = build_inspector(intake, layout.exit);
|
||||
let (body_locals, mut exit_live) =
|
||||
classify_body_and_exit(var_classes, intake, &inspector, &layout);
|
||||
|
||||
let exit_live_from_box =
|
||||
merge_exit_live_from_box(exit_live_box, query, intake, layout.exit);
|
||||
for name in exit_live_from_box {
|
||||
exit_live.insert(name);
|
||||
}
|
||||
let variable_definitions = collect_variable_definitions(intake, &layout);
|
||||
let (body_locals, exit_live) = classify_body_and_exit(
|
||||
intake,
|
||||
&pinned,
|
||||
&carriers,
|
||||
&variable_definitions,
|
||||
&layout,
|
||||
);
|
||||
|
||||
let progress_carrier = carriers.iter().next().cloned();
|
||||
let variable_definitions = extract_variable_definitions(&inspector);
|
||||
|
||||
Some(Self {
|
||||
header: layout.header,
|
||||
@ -199,43 +131,61 @@ fn block_layout(loop_form: &LoopForm) -> LoopBlockLayout {
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 48-6: Trio 依存 helper(LocalScopeInspectorBox 構築)
|
||||
///
|
||||
/// LocalScopeInspectorBox に header/exit snapshot を登録して変数定義解析の土台を作る。
|
||||
/// この関数は from_existing_boxes_legacy からのみ呼ばれ、Trio を直接扱う。
|
||||
fn build_inspector(intake: &LoopFormIntake, exit: BasicBlockId) -> LocalScopeInspectorBox {
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
inspector.record_snapshot(exit, &intake.header_snapshot);
|
||||
for (bb, snap) in &intake.exit_snapshots {
|
||||
inspector.record_snapshot(*bb, snap);
|
||||
fn collect_variable_definitions(
|
||||
intake: &LoopFormIntake,
|
||||
layout: &LoopBlockLayout,
|
||||
) -> BTreeMap<String, BTreeSet<BasicBlockId>> {
|
||||
let mut var_defs: BTreeMap<String, BTreeSet<BasicBlockId>> = BTreeMap::new();
|
||||
|
||||
for var_name in intake.header_snapshot.keys() {
|
||||
var_defs
|
||||
.entry(var_name.clone())
|
||||
.or_default()
|
||||
.insert(layout.exit);
|
||||
var_defs
|
||||
.entry(var_name.clone())
|
||||
.or_default()
|
||||
.insert(layout.header);
|
||||
}
|
||||
inspector
|
||||
|
||||
for (bb, snap) in &intake.exit_snapshots {
|
||||
for var_name in snap.keys() {
|
||||
var_defs
|
||||
.entry(var_name.clone())
|
||||
.or_default()
|
||||
.insert(*bb);
|
||||
}
|
||||
}
|
||||
|
||||
var_defs
|
||||
}
|
||||
|
||||
/// Phase 48-6: Trio 依存 helper(LoopVarClassBox による変数分類)
|
||||
///
|
||||
/// LoopVarClassBox::classify_all() を呼び出して変数を 4分類し、
|
||||
/// body_locals と exit_live に振り分ける。
|
||||
/// この関数は from_existing_boxes_legacy からのみ呼ばれ、Trio を直接扱う。
|
||||
fn classify_body_and_exit(
|
||||
var_classes: &LoopVarClassBox,
|
||||
intake: &LoopFormIntake,
|
||||
inspector: &LocalScopeInspectorBox,
|
||||
pinned: &BTreeSet<String>,
|
||||
carriers: &BTreeSet<String>,
|
||||
variable_definitions: &BTreeMap<String, BTreeSet<BasicBlockId>>,
|
||||
layout: &LoopBlockLayout,
|
||||
) -> (BTreeSet<String>, BTreeSet<String>) {
|
||||
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 {
|
||||
let mut all_names: BTreeSet<String> = intake.header_snapshot.keys().cloned().collect();
|
||||
for (_, snap) in &intake.exit_snapshots {
|
||||
all_names.extend(snap.keys().cloned());
|
||||
}
|
||||
all_names.extend(pinned.iter().cloned());
|
||||
all_names.extend(carriers.iter().cloned());
|
||||
|
||||
for name in all_names {
|
||||
let class = classify_var(
|
||||
&name,
|
||||
pinned,
|
||||
carriers,
|
||||
&intake.exit_preds,
|
||||
variable_definitions,
|
||||
);
|
||||
|
||||
match class {
|
||||
LoopVarClass::Pinned | LoopVarClass::Carrier => {
|
||||
exit_live.insert(name.clone());
|
||||
@ -263,40 +213,42 @@ fn classify_body_and_exit(
|
||||
(body_locals, exit_live)
|
||||
}
|
||||
|
||||
/// Phase 48-6: Trio 依存 helper(LoopExitLivenessBox による exit_live 計算)
|
||||
///
|
||||
/// LoopExitLivenessBox::compute_live_at_exit() を呼び出して、
|
||||
/// ループ終了時に live な変数集合を取得する。
|
||||
/// この関数は from_existing_boxes_legacy からのみ呼ばれ、Trio を直接扱う。
|
||||
fn merge_exit_live_from_box(
|
||||
exit_live_box: &LoopExitLivenessBox,
|
||||
query: &impl MirQuery,
|
||||
intake: &LoopFormIntake,
|
||||
exit_block: BasicBlockId,
|
||||
) -> BTreeSet<String> {
|
||||
exit_live_box.compute_live_at_exit(
|
||||
query,
|
||||
exit_block,
|
||||
&intake.header_snapshot,
|
||||
&intake.exit_snapshots,
|
||||
)
|
||||
fn classify_var(
|
||||
var_name: &str,
|
||||
pinned: &BTreeSet<String>,
|
||||
carriers: &BTreeSet<String>,
|
||||
exit_preds: &[BasicBlockId],
|
||||
variable_definitions: &BTreeMap<String, BTreeSet<BasicBlockId>>,
|
||||
) -> LoopVarClass {
|
||||
if var_name.starts_with("__pin$") && var_name.contains("$@") {
|
||||
return LoopVarClass::BodyLocalInternal;
|
||||
}
|
||||
|
||||
if pinned.contains(var_name) {
|
||||
return LoopVarClass::Pinned;
|
||||
}
|
||||
|
||||
if carriers.contains(var_name) {
|
||||
return LoopVarClass::Carrier;
|
||||
}
|
||||
|
||||
if is_available_in_all(var_name, exit_preds, variable_definitions) {
|
||||
LoopVarClass::BodyLocalExit
|
||||
} else {
|
||||
LoopVarClass::BodyLocalInternal
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 48-4: LocalScopeInspectorBox から variable_definitions を抽出
|
||||
///
|
||||
/// inspector.all_variables() と get_defining_blocks() を使って、
|
||||
/// 変数名 → 定義ブロック集合のマッピングを構築する。
|
||||
/// この関数は from_existing_boxes_legacy からのみ呼ばれ、Trio を直接扱う。
|
||||
fn extract_variable_definitions(
|
||||
inspector: &LocalScopeInspectorBox,
|
||||
) -> BTreeMap<String, BTreeSet<BasicBlockId>> {
|
||||
let mut var_defs = BTreeMap::new();
|
||||
for var_name in inspector.all_variables() {
|
||||
let blocks: BTreeSet<BasicBlockId> = inspector
|
||||
.get_defining_blocks(&var_name)
|
||||
.into_iter()
|
||||
.collect();
|
||||
var_defs.insert(var_name, blocks);
|
||||
fn is_available_in_all(
|
||||
var_name: &str,
|
||||
required_blocks: &[BasicBlockId],
|
||||
variable_definitions: &BTreeMap<String, BTreeSet<BasicBlockId>>,
|
||||
) -> bool {
|
||||
if let Some(defining_blocks) = variable_definitions.get(var_name) {
|
||||
required_blocks
|
||||
.iter()
|
||||
.all(|block| defining_blocks.contains(block))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
var_defs
|
||||
}
|
||||
|
||||
@ -16,7 +16,6 @@ mod structural;
|
||||
pub(crate) use case_a::is_case_a_minimal_target;
|
||||
pub(crate) use context::CaseAContext;
|
||||
pub(crate) use shape::LoopScopeShape;
|
||||
pub(crate) use structural::LoopStructuralAnalysis;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
@ -4,9 +4,30 @@
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use crate::mir::phi_core::loop_var_classifier::LoopVarClass;
|
||||
use crate::mir::BasicBlockId;
|
||||
|
||||
/// Variable classification for loop PHI generation.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LoopVarClass {
|
||||
Pinned,
|
||||
Carrier,
|
||||
BodyLocalExit,
|
||||
BodyLocalInternal,
|
||||
}
|
||||
|
||||
impl LoopVarClass {
|
||||
#[cfg(test)]
|
||||
pub fn needs_exit_phi(self) -> bool {
|
||||
matches!(self, LoopVarClass::Pinned | LoopVarClass::Carrier | LoopVarClass::BodyLocalExit)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn needs_header_phi(self) -> bool {
|
||||
matches!(self, LoopVarClass::Pinned | LoopVarClass::Carrier)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// ループ変数スコープの統合ビュー
|
||||
///
|
||||
/// ## 4分類の定義
|
||||
@ -32,7 +53,7 @@ use crate::mir::BasicBlockId;
|
||||
/// - Block IDs: `header`, `body`, `latch`, `exit`
|
||||
/// - Variable classification: `pinned`, `carriers`, `body_locals`, `exit_live`
|
||||
/// - `progress_carrier`: 進捗チェック用(将来の Verifier で使用予定)
|
||||
/// - `variable_definitions`: LocalScopeInspectorBox 情報統合予定(Phase 48-5+)
|
||||
/// - `variable_definitions`: definition blocks collected from LoopFormIntake snapshots
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)] // Block IDs and progress_carrier are reserved for future F-3/F-4 use
|
||||
pub(crate) struct LoopScopeShape {
|
||||
@ -50,12 +71,13 @@ pub(crate) struct LoopScopeShape {
|
||||
|
||||
impl LoopScopeShape {
|
||||
/// header PHI が必要か判定
|
||||
#[allow(dead_code)] // Phase 30: will be used in F-3 generic lowering
|
||||
#[cfg(test)]
|
||||
pub fn needs_header_phi(&self, var_name: &str) -> bool {
|
||||
self.pinned.contains(var_name) || self.carriers.contains(var_name)
|
||||
}
|
||||
|
||||
/// exit PHI が必要か判定
|
||||
#[cfg(test)]
|
||||
pub fn needs_exit_phi(&self, var_name: &str) -> bool {
|
||||
self.exit_live.contains(var_name)
|
||||
}
|
||||
@ -70,21 +92,8 @@ impl LoopScopeShape {
|
||||
self.carriers.iter().cloned().collect()
|
||||
}
|
||||
|
||||
/// header PHI 対象(pinned + carriers)
|
||||
#[allow(dead_code)] // Phase 30: will be used in F-3 generic lowering
|
||||
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
|
||||
}
|
||||
|
||||
/// exit PHI 対象
|
||||
#[allow(dead_code)] // Phase 30: will be used in F-3 generic lowering
|
||||
pub fn exit_phi_vars(&self) -> Vec<String> {
|
||||
self.exit_live.iter().cloned().collect()
|
||||
}
|
||||
|
||||
/// 変数を4分類
|
||||
#[cfg(test)]
|
||||
pub fn classify(&self, var_name: &str) -> LoopVarClass {
|
||||
if self.pinned.contains(var_name) {
|
||||
return LoopVarClass::Pinned;
|
||||
@ -105,20 +114,14 @@ impl LoopScopeShape {
|
||||
}
|
||||
}
|
||||
|
||||
/// 複数変数を一括分類
|
||||
pub fn classify_all(&self, var_names: &[String]) -> Vec<(String, LoopVarClass)> {
|
||||
var_names
|
||||
.iter()
|
||||
.map(|name| (name.clone(), self.classify(name)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// ループ終了時に live な変数集合を返す
|
||||
#[cfg(test)]
|
||||
pub fn get_exit_live(&self) -> &BTreeSet<String> {
|
||||
&self.exit_live
|
||||
}
|
||||
|
||||
/// 変数が required_blocks すべてで利用可能か判定
|
||||
#[cfg(test)]
|
||||
pub fn is_available_in_all(&self, var_name: &str, required_blocks: &[BasicBlockId]) -> bool {
|
||||
if let Some(def_blocks) = self.variable_definitions.get(var_name) {
|
||||
required_blocks.iter().all(|bid| def_blocks.contains(bid))
|
||||
|
||||
@ -108,7 +108,7 @@ impl LoopStructuralAnalysis {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::control_form::{ExitEdge, ExitKind, LoopId};
|
||||
use crate::mir::{BasicBlockId, ValueId};
|
||||
use crate::mir::BasicBlockId;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
fn make_single_exit_analysis() -> ExitAnalysis {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
use super::*;
|
||||
use crate::mir::join_ir::lowering::loop_form_intake::LoopFormIntake;
|
||||
use crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox;
|
||||
use crate::mir::phi_core::loop_var_classifier::{LoopVarClass, LoopVarClassBox};
|
||||
use super::shape::LoopVarClass;
|
||||
use crate::mir::{BasicBlockId, MirQuery, ValueId};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
@ -49,21 +48,12 @@ impl MirQuery for EmptyQuery {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_existing_boxes_basic() {
|
||||
fn test_from_loop_form_basic() {
|
||||
let loop_form = make_dummy_loop_form();
|
||||
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(
|
||||
&loop_form,
|
||||
&intake,
|
||||
&var_classes,
|
||||
&exit_live_box,
|
||||
&query,
|
||||
None,
|
||||
);
|
||||
let scope = LoopScopeShape::from_loop_form(&loop_form, &intake, &query, None);
|
||||
|
||||
assert!(scope.is_some());
|
||||
let scope = scope.unwrap();
|
||||
@ -87,19 +77,9 @@ fn test_from_existing_boxes_basic() {
|
||||
fn test_needs_header_phi() {
|
||||
let loop_form = make_dummy_loop_form();
|
||||
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(
|
||||
&loop_form,
|
||||
&intake,
|
||||
&var_classes,
|
||||
&exit_live_box,
|
||||
&query,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let scope = LoopScopeShape::from_loop_form(&loop_form, &intake, &query, None).unwrap();
|
||||
|
||||
assert!(scope.needs_header_phi("s"));
|
||||
assert!(scope.needs_header_phi("n"));
|
||||
@ -111,19 +91,9 @@ fn test_needs_header_phi() {
|
||||
fn test_needs_exit_phi() {
|
||||
let loop_form = make_dummy_loop_form();
|
||||
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(
|
||||
&loop_form,
|
||||
&intake,
|
||||
&var_classes,
|
||||
&exit_live_box,
|
||||
&query,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let scope = LoopScopeShape::from_loop_form(&loop_form, &intake, &query, None).unwrap();
|
||||
|
||||
assert!(scope.needs_exit_phi("s"));
|
||||
assert!(scope.needs_exit_phi("n"));
|
||||
@ -134,19 +104,9 @@ fn test_needs_exit_phi() {
|
||||
fn test_ordered_accessors() {
|
||||
let loop_form = make_dummy_loop_form();
|
||||
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(
|
||||
&loop_form,
|
||||
&intake,
|
||||
&var_classes,
|
||||
&exit_live_box,
|
||||
&query,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let scope = LoopScopeShape::from_loop_form(&loop_form, &intake, &query, None).unwrap();
|
||||
|
||||
let pinned = scope.pinned_ordered();
|
||||
assert_eq!(pinned.len(), 2);
|
||||
@ -197,19 +157,9 @@ fn test_block_ids_preserved() {
|
||||
};
|
||||
|
||||
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(
|
||||
&loop_form,
|
||||
&intake,
|
||||
&var_classes,
|
||||
&exit_live_box,
|
||||
&query,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let scope = LoopScopeShape::from_loop_form(&loop_form, &intake, &query, None).unwrap();
|
||||
|
||||
assert_eq!(scope.header, BasicBlockId::new(200));
|
||||
assert_eq!(scope.body, BasicBlockId::new(300));
|
||||
@ -245,19 +195,9 @@ fn test_deterministic_order() {
|
||||
fn test_needs_phi_consistency() {
|
||||
let loop_form = make_dummy_loop_form();
|
||||
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(
|
||||
&loop_form,
|
||||
&intake,
|
||||
&var_classes,
|
||||
&exit_live_box,
|
||||
&query,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let scope = LoopScopeShape::from_loop_form(&loop_form, &intake, &query, None).unwrap();
|
||||
|
||||
for var in &scope.pinned {
|
||||
assert!(
|
||||
@ -458,7 +398,7 @@ fn test_is_available_in_all_phase48_5_future() {
|
||||
assert!(!scope.is_available_in_all("unknown", &[BasicBlockId::new(3)]));
|
||||
}
|
||||
|
||||
/// Phase 48-4: from_existing_boxes で variable_definitions が埋まることを確認
|
||||
/// Phase 48-4: from_loop_form で variable_definitions が埋まることを確認
|
||||
#[test]
|
||||
fn test_variable_definitions_from_inspector() {
|
||||
let loop_form = make_dummy_loop_form();
|
||||
@ -481,19 +421,9 @@ fn test_variable_definitions_from_inspector() {
|
||||
(BasicBlockId::new(11), exit2_snap),
|
||||
];
|
||||
|
||||
let var_classes = LoopVarClassBox::new();
|
||||
let exit_live_box = LoopExitLivenessBox::new();
|
||||
let query = EmptyQuery;
|
||||
|
||||
let scope = LoopScopeShape::from_existing_boxes(
|
||||
&loop_form,
|
||||
&intake,
|
||||
&var_classes,
|
||||
&exit_live_box,
|
||||
&query,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let scope = LoopScopeShape::from_loop_form(&loop_form, &intake, &query, None).unwrap();
|
||||
|
||||
// s, n, i は両方の exit で利用可能 → is_available_in_all should be true
|
||||
assert!(scope.is_available_in_all("s", &[BasicBlockId::new(10), BasicBlockId::new(11)]));
|
||||
@ -532,19 +462,9 @@ fn test_variable_definitions_partial_availability() {
|
||||
(BasicBlockId::new(22), exit3_snap),
|
||||
];
|
||||
|
||||
let var_classes = LoopVarClassBox::new();
|
||||
let exit_live_box = LoopExitLivenessBox::new();
|
||||
let query = EmptyQuery;
|
||||
|
||||
let scope = LoopScopeShape::from_existing_boxes(
|
||||
&loop_form,
|
||||
&intake,
|
||||
&var_classes,
|
||||
&exit_live_box,
|
||||
&query,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let scope = LoopScopeShape::from_loop_form(&loop_form, &intake, &query, None).unwrap();
|
||||
|
||||
// s は全 exit で利用可能
|
||||
assert!(scope.is_available_in_all(
|
||||
|
||||
@ -98,8 +98,8 @@ impl LoopToJoinLowerer {
|
||||
let query = MirQueryBox::new(func);
|
||||
|
||||
// Step 2: LoopFormIntake を構築
|
||||
// Phase 48-2: intake_loop_form は空の var_classes を使用(将来は from_loop_form に統合)
|
||||
let intake = intake_loop_form(loop_form, &Default::default(), &query, func)?;
|
||||
// Phase 70-2: var_classes 引数削除完了(Trio 依存ゼロ)
|
||||
let intake = intake_loop_form(loop_form, &query, func)?;
|
||||
|
||||
// Step 3: LoopScopeShape を構築
|
||||
// Phase 48-2: from_loop_form() で Trio を内部化(LoopExitLivenessBox 依存削除)
|
||||
|
||||
@ -114,7 +114,7 @@ pub(crate) fn env_flag_is_1(name: &str) -> bool {
|
||||
/// - **Pinned**: ループ中で値が変わらない変数(例: skip_ws の s, n / trim の str, b)
|
||||
/// - **Carrier**: ループで更新される変数(例: skip_ws の i / trim の e)
|
||||
///
|
||||
/// Phase 27.4 では minimal/trim 用に手動で構成するが、将来は LoopVarClassBox から自動導出する。
|
||||
/// Phase 27.4 では minimal/trim 用に手動で構成するが、将来は LoopScopeShape から自動導出する。
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)] // Phase 27.4-C で実際に使用予定(現在は設計の雛形)
|
||||
pub(crate) struct LoopHeaderShape {
|
||||
|
||||
@ -316,7 +316,7 @@ impl<'a> LoopBuilder<'a> {
|
||||
// - BodyLocalExit: needs EXIT PHI only, NOT header PHI
|
||||
// - BodyLocalInternal: needs NO PHI at all
|
||||
//
|
||||
// TODO Step 5-3: Integrate Option C classification (LoopVarClassBox) here
|
||||
// TODO Step 5-3: Integrate Option C classification (LoopScopeShape) here
|
||||
//
|
||||
// TEMPORARY DISABLE to test hypothesis that header PHIs are the root cause
|
||||
let trace_loop_phi = std::env::var("HAKO_LOOP_PHI_TRACE").ok().as_deref() == Some("1");
|
||||
|
||||
@ -1,361 +0,0 @@
|
||||
/// LocalScopeInspectorBox - Variable definition tracker
|
||||
///
|
||||
/// # Phase 30: LoopScopeShape 移行中
|
||||
///
|
||||
/// このBoxは将来 LoopScopeShape に吸収される予定。
|
||||
/// 定義位置情報は LoopScopeShape::from_existing_boxes() 内部で集約され、
|
||||
/// 変数分類に使用される。
|
||||
///
|
||||
/// **新規コード**: LoopScopeShape が利用可能な場合は、
|
||||
/// `classify()` メソッドを直接使うことを推奨。
|
||||
///
|
||||
/// # Purpose
|
||||
///
|
||||
/// This box tracks which variables are defined in which basic blocks.
|
||||
/// Used by LoopVarClassBox to determine if a body-local variable is
|
||||
/// defined in all exit predecessors (making it eligible for exit PHI).
|
||||
///
|
||||
/// # Responsibility
|
||||
///
|
||||
/// - Record variable definitions per block
|
||||
/// - Query if a variable is defined in specific blocks
|
||||
/// - Query if a variable is defined in ALL of a set of blocks
|
||||
///
|
||||
/// # Design Philosophy (Box Theory)
|
||||
///
|
||||
/// This box has ONE job: track definition locations.
|
||||
/// It doesn't know about loops, PHI nodes, or exit blocks.
|
||||
/// It's a pure data structure that other boxes can query.
|
||||
use crate::mir::{BasicBlockId, ValueId};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
/// Tracks which variables are defined in which blocks
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct LocalScopeInspectorBox {
|
||||
/// Variable name → Set of blocks where it's defined
|
||||
var_definitions: BTreeMap<String, BTreeSet<BasicBlockId>>,
|
||||
}
|
||||
|
||||
impl LocalScopeInspectorBox {
|
||||
/// Create a new empty inspector
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
var_definitions: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Record that a variable is defined in a specific block
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// let mut inspector = LocalScopeInspectorBox::new();
|
||||
/// inspector.record_definition("ch", BasicBlockId::new(5));
|
||||
/// inspector.record_definition("i", BasicBlockId::new(2));
|
||||
/// inspector.record_definition("i", BasicBlockId::new(5)); // i defined in multiple blocks
|
||||
/// ```
|
||||
pub fn record_definition(&mut self, var_name: &str, block: BasicBlockId) {
|
||||
self.var_definitions
|
||||
.entry(var_name.to_string())
|
||||
.or_insert_with(BTreeSet::new)
|
||||
.insert(block);
|
||||
}
|
||||
|
||||
/// Record definitions from a snapshot (block_id, vars)
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// let snapshot = BTreeMap::from([
|
||||
/// ("i".to_string(), ValueId(1)),
|
||||
/// ("n".to_string(), ValueId(2)),
|
||||
/// ]);
|
||||
/// inspector.record_snapshot(BasicBlockId::new(5), &snapshot);
|
||||
/// ```
|
||||
pub fn record_snapshot(&mut self, block: BasicBlockId, vars: &BTreeMap<String, ValueId>) {
|
||||
for var_name in vars.keys() {
|
||||
self.record_definition(var_name, block);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a variable is defined in a specific block
|
||||
///
|
||||
/// # Returns
|
||||
/// - `true` if the variable is defined in the block
|
||||
/// - `false` if the variable is not defined in the block, or doesn't exist
|
||||
pub fn is_defined_in(&self, var_name: &str, block: BasicBlockId) -> bool {
|
||||
self.var_definitions
|
||||
.get(var_name)
|
||||
.map(|blocks| blocks.contains(&block))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Check if a variable is AVAILABLE in ALL of the specified blocks
|
||||
///
|
||||
/// # Semantics (Important!)
|
||||
///
|
||||
/// This checks if the variable is "in scope" (available) at each block,
|
||||
/// NOT whether it was "newly defined" in that block.
|
||||
///
|
||||
/// - A variable is "available" if it's in the snapshot at that point
|
||||
/// - This includes both newly-defined variables AND inherited variables
|
||||
///
|
||||
/// For Option C PHI generation:
|
||||
/// - "available in all exit preds" → can generate exit PHI
|
||||
/// - "NOT available in all exit preds" → BodyLocalInternal, skip exit PHI
|
||||
///
|
||||
/// # Returns
|
||||
/// - `true` if the variable is available in every block in `required_blocks`
|
||||
/// - `false` if the variable is missing from any block, or doesn't exist
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// // Variable "ch" is only available in block 5
|
||||
/// inspector.record_definition("ch", BasicBlockId::new(5));
|
||||
///
|
||||
/// let exit_preds = vec![BasicBlockId::new(2), BasicBlockId::new(5)];
|
||||
/// assert!(!inspector.is_available_in_all("ch", &exit_preds)); // false: missing block 2
|
||||
///
|
||||
/// // Variable "i" is available in both blocks
|
||||
/// inspector.record_definition("i", BasicBlockId::new(2));
|
||||
/// inspector.record_definition("i", BasicBlockId::new(5));
|
||||
/// assert!(inspector.is_available_in_all("i", &exit_preds)); // true: in all blocks
|
||||
/// ```
|
||||
pub fn is_available_in_all(&self, var_name: &str, required_blocks: &[BasicBlockId]) -> bool {
|
||||
if let Some(defining_blocks) = self.var_definitions.get(var_name) {
|
||||
required_blocks
|
||||
.iter()
|
||||
.all(|block| defining_blocks.contains(block))
|
||||
} else {
|
||||
// Variable doesn't exist at all
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all blocks where a variable is defined
|
||||
///
|
||||
/// # Returns
|
||||
/// - Vec of BasicBlockIds where the variable is defined
|
||||
/// - Empty vec if the variable doesn't exist
|
||||
pub fn get_defining_blocks(&self, var_name: &str) -> Vec<BasicBlockId> {
|
||||
self.var_definitions
|
||||
.get(var_name)
|
||||
.map(|blocks| blocks.iter().copied().collect())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Get all tracked variables
|
||||
pub fn all_variables(&self) -> Vec<String> {
|
||||
self.var_definitions.keys().cloned().collect()
|
||||
}
|
||||
|
||||
/// Get the number of blocks where a variable is defined
|
||||
pub fn definition_count(&self, var_name: &str) -> usize {
|
||||
self.var_definitions
|
||||
.get(var_name)
|
||||
.map(|blocks| blocks.len())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Phase 30 F-1.3: LoopScopeShape 委譲メソッド
|
||||
// ========================================================================
|
||||
|
||||
/// Phase 30: LoopScopeShape を使って変数が全 exit pred で利用可能か判定
|
||||
///
|
||||
/// 新規コードでは LoopScopeShape::classify() を直接使うことを推奨。
|
||||
/// このメソッドは LoopScopeShape が既にある場合の便利メソッド。
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// LoopScopeShape::classify() は既に exit_live 情報を含んでいるため、
|
||||
/// 直接 classify() → needs_exit_phi() を使う方が効率的。
|
||||
#[allow(dead_code)] // Phase 30 F-1.3: will be used when LocalScopeInspectorBox is absorbed
|
||||
pub(crate) fn is_available_in_all_with_scope(
|
||||
&self,
|
||||
var_name: &str,
|
||||
scope: &crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
|
||||
) -> bool {
|
||||
// LoopScopeShape の分類を使用
|
||||
// Pinned/Carrier/BodyLocalExit → available in all (exit PHI needed)
|
||||
// BodyLocalInternal → NOT available in all (no exit PHI)
|
||||
scope.needs_exit_phi(var_name)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Unit Tests
|
||||
// ============================================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_record_and_query_single_definition() {
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
|
||||
inspector.record_definition("ch", BasicBlockId::new(5));
|
||||
|
||||
assert!(inspector.is_defined_in("ch", BasicBlockId::new(5)));
|
||||
assert!(!inspector.is_defined_in("ch", BasicBlockId::new(2)));
|
||||
assert!(!inspector.is_defined_in("unknown", BasicBlockId::new(5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_multiple_blocks() {
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
|
||||
inspector.record_definition("i", BasicBlockId::new(2));
|
||||
inspector.record_definition("i", BasicBlockId::new(5));
|
||||
inspector.record_definition("i", BasicBlockId::new(7));
|
||||
|
||||
assert!(inspector.is_defined_in("i", BasicBlockId::new(2)));
|
||||
assert!(inspector.is_defined_in("i", BasicBlockId::new(5)));
|
||||
assert!(inspector.is_defined_in("i", BasicBlockId::new(7)));
|
||||
assert!(!inspector.is_defined_in("i", BasicBlockId::new(10)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_available_in_all_success() {
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
|
||||
inspector.record_definition("i", BasicBlockId::new(2));
|
||||
inspector.record_definition("i", BasicBlockId::new(5));
|
||||
inspector.record_definition("i", BasicBlockId::new(7));
|
||||
|
||||
let required = vec![BasicBlockId::new(2), BasicBlockId::new(5)];
|
||||
|
||||
assert!(inspector.is_available_in_all("i", &required));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_available_in_all_failure() {
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
|
||||
// "ch" only defined in block 5
|
||||
inspector.record_definition("ch", BasicBlockId::new(5));
|
||||
|
||||
let required = vec![BasicBlockId::new(2), BasicBlockId::new(5)];
|
||||
|
||||
// Should fail because block 2 is missing
|
||||
assert!(!inspector.is_available_in_all("ch", &required));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_available_in_all_unknown_variable() {
|
||||
let inspector = LocalScopeInspectorBox::new();
|
||||
|
||||
let required = vec![BasicBlockId::new(2)];
|
||||
|
||||
assert!(!inspector.is_available_in_all("unknown", &required));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_defining_blocks() {
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
|
||||
inspector.record_definition("x", BasicBlockId::new(3));
|
||||
inspector.record_definition("x", BasicBlockId::new(7));
|
||||
inspector.record_definition("x", BasicBlockId::new(1));
|
||||
|
||||
let mut blocks = inspector.get_defining_blocks("x");
|
||||
blocks.sort_by_key(|b| b.0);
|
||||
|
||||
assert_eq!(
|
||||
blocks,
|
||||
vec![
|
||||
BasicBlockId::new(1),
|
||||
BasicBlockId::new(3),
|
||||
BasicBlockId::new(7),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_defining_blocks_unknown() {
|
||||
let inspector = LocalScopeInspectorBox::new();
|
||||
|
||||
assert_eq!(
|
||||
inspector.get_defining_blocks("unknown"),
|
||||
Vec::<BasicBlockId>::new()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_snapshot() {
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
|
||||
let mut snapshot = BTreeMap::new();
|
||||
snapshot.insert("i".to_string(), ValueId(10));
|
||||
snapshot.insert("n".to_string(), ValueId(20));
|
||||
snapshot.insert("ch".to_string(), ValueId(712));
|
||||
|
||||
inspector.record_snapshot(BasicBlockId::new(5), &snapshot);
|
||||
|
||||
assert!(inspector.is_defined_in("i", BasicBlockId::new(5)));
|
||||
assert!(inspector.is_defined_in("n", BasicBlockId::new(5)));
|
||||
assert!(inspector.is_defined_in("ch", BasicBlockId::new(5)));
|
||||
assert!(!inspector.is_defined_in("i", BasicBlockId::new(2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_variables() {
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
|
||||
inspector.record_definition("a", BasicBlockId::new(1));
|
||||
inspector.record_definition("b", BasicBlockId::new(2));
|
||||
inspector.record_definition("c", BasicBlockId::new(3));
|
||||
|
||||
let mut vars = inspector.all_variables();
|
||||
vars.sort();
|
||||
|
||||
assert_eq!(vars, vec!["a", "b", "c"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_definition_count() {
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
|
||||
inspector.record_definition("x", BasicBlockId::new(1));
|
||||
inspector.record_definition("x", BasicBlockId::new(2));
|
||||
inspector.record_definition("x", BasicBlockId::new(3));
|
||||
|
||||
assert_eq!(inspector.definition_count("x"), 3);
|
||||
assert_eq!(inspector.definition_count("unknown"), 0);
|
||||
}
|
||||
|
||||
/// Test the skip_whitespace PHI bug scenario
|
||||
#[test]
|
||||
fn test_skip_whitespace_scenario() {
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
|
||||
// Simulate skip_whitespace:
|
||||
// - Variables i, n, s are defined in all blocks (header + breaks)
|
||||
// - Variable ch is only defined in block 5 (break path 2)
|
||||
|
||||
let block_2 = BasicBlockId::new(2); // header / break path 1
|
||||
let block_5 = BasicBlockId::new(5); // break path 2
|
||||
let block_7 = BasicBlockId::new(7); // latch (hypothetical)
|
||||
|
||||
// i, n, s are in all blocks
|
||||
for var in &["i", "n", "s"] {
|
||||
inspector.record_definition(var, block_2);
|
||||
inspector.record_definition(var, block_5);
|
||||
inspector.record_definition(var, block_7);
|
||||
}
|
||||
|
||||
// ch is only in block 5
|
||||
inspector.record_definition("ch", block_5);
|
||||
|
||||
let exit_preds = vec![block_2, block_5, block_7];
|
||||
|
||||
// i, n, s should be in all exit preds
|
||||
assert!(inspector.is_available_in_all("i", &exit_preds));
|
||||
assert!(inspector.is_available_in_all("n", &exit_preds));
|
||||
assert!(inspector.is_available_in_all("s", &exit_preds));
|
||||
|
||||
// ch should NOT be in all exit preds (missing block 2 and 7)
|
||||
assert!(!inspector.is_available_in_all("ch", &exit_preds));
|
||||
|
||||
// This means ch should NOT get an exit PHI!
|
||||
}
|
||||
}
|
||||
@ -1,414 +0,0 @@
|
||||
//! Loop Exit Liveness Box - Exit後で使われる変数の決定専門Box
|
||||
//!
|
||||
//! Phase 26-F-4: Exit Liveness Analysis
|
||||
//! - Exit後で本当に使われる変数を決定
|
||||
//! - Phase 1: 空集合(MIRスキャン実装待ち)
|
||||
//! - Phase 2+: MIRスキャン実装予定(LoopFormOps拡張後)
|
||||
//!
|
||||
//! # 環境変数制御
|
||||
//! - `NYASH_EXIT_LIVE_ENABLE=1`: Phase 2+ MIRスキャン実装を有効化(実験用)
|
||||
//! - デフォルト(未設定): Phase 26-F-3 相当の挙動(安定版)
|
||||
//!
|
||||
//! # Box-First理論: 責務の明確な分離
|
||||
//!
|
||||
//! - **LoopVarClassBox**: スコープ分類(Pinned/Carrier/BodyLocal)
|
||||
//! - **LoopExitLivenessBox**: Exit後の実際の使用(この箱)
|
||||
//! - **BodyLocalPhiBuilder**: 両者を統合して exit PHI 判定
|
||||
//!
|
||||
//! # Phase 26-F-4: ChatGPT設計による箱離婚
|
||||
//!
|
||||
//! **問題**: LoopVarClassBox が「スコープ分類」と「exit後使用」を混在
|
||||
//!
|
||||
//! **解決**: LoopExitLivenessBox 新設で完全分離
|
||||
//! - スコープ分類: LoopVarClassBox(変更なし)
|
||||
//! - 実際の使用: LoopExitLivenessBox(新箱)
|
||||
//! - 統合判定: BodyLocalPhiBuilder(OR論理)
|
||||
|
||||
use crate::mir::{BasicBlockId, ValueId};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
/// ExitLivenessProvider
|
||||
///
|
||||
/// ExitPhiBuilder が依存するインターフェース。環境や実装を差し替えやすくするために
|
||||
/// trait として定義し、Legacy / MIR スキャン版のどちらでも差し込めるようにしている。
|
||||
pub trait ExitLivenessProvider: Send + Sync {
|
||||
fn compute_live_at_exit(
|
||||
&self,
|
||||
mir_query: &dyn crate::mir::MirQuery,
|
||||
exit_block: BasicBlockId,
|
||||
header_vals: &BTreeMap<String, ValueId>,
|
||||
exit_snapshots: &[(BasicBlockId, BTreeMap<String, ValueId>)],
|
||||
) -> BTreeSet<String>;
|
||||
}
|
||||
|
||||
/// Loop Exit Liveness Box(Legacy/Phase 1)
|
||||
///
|
||||
/// # Phase 30: LoopScopeShape 移行中
|
||||
///
|
||||
/// このBoxは将来 LoopScopeShape に吸収される予定。
|
||||
/// LoopScopeShape.exit_live が唯一の live_at_exit 情報源になる。
|
||||
///
|
||||
/// **新規コード**: `LoopScopeShape::exit_live` を直接参照すること。
|
||||
///
|
||||
/// # Purpose
|
||||
/// Exit後で本当に使われる変数を決定する専門箱
|
||||
///
|
||||
/// # Responsibility
|
||||
/// - live_at_exit 変数集合の計算
|
||||
/// - 将来: MIRスキャンによる精密liveness解析
|
||||
/// - 現在: 保守的近似(全変数をliveとみなす)
|
||||
///
|
||||
/// # Phase 1 Implementation (Conservative)
|
||||
/// header_vals + exit_snapshots に現れる全変数を live_at_exit に含める
|
||||
///
|
||||
/// **理由**: LoopFormOps に MIR アクセスがないため、保守的近似で先行実装
|
||||
///
|
||||
/// # Future Phase 2+ (Precise)
|
||||
/// exit ブロックの MIR をスキャンして、実際に read される変数のみを live とする
|
||||
///
|
||||
/// **将来拡張**: LoopFormOps 拡張 or MirFunction 参照追加時に精密化
|
||||
///
|
||||
/// # Usage
|
||||
/// ```ignore
|
||||
/// let liveness_box = LoopExitLivenessBox::new();
|
||||
/// let live_at_exit = liveness_box.compute_live_at_exit(
|
||||
/// &header_vals,
|
||||
/// &exit_snapshots,
|
||||
/// );
|
||||
/// // → ch, pname, pend 等が含まれる(保守的)
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct LoopExitLivenessBox;
|
||||
|
||||
impl LoopExitLivenessBox {
|
||||
/// Create new LoopExitLivenessBox
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// Compute live_at_exit variables (Phase 1: Conservative)
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `header_vals` - Header variable values
|
||||
/// * `exit_snapshots` - Exit predecessor snapshots
|
||||
///
|
||||
/// # Returns
|
||||
/// BTreeSet of variable names that are potentially live at exit
|
||||
///
|
||||
/// # Phase 1 Logic (Conservative)
|
||||
/// すべての header_vals と exit_snapshots に現れる変数を live とみなす
|
||||
///
|
||||
/// **保守的な理由**:
|
||||
/// - BodyLocalInternal でも exit 後で使われる変数がある(ch, pname, pend)
|
||||
/// - MIR スキャンなしでは正確な判定不可
|
||||
/// - Phase 35-5: phi_invariants.rs deleted (validation moved to JoinIR Verifier)
|
||||
///
|
||||
/// # Future Phase 2+ (Precise)
|
||||
/// exit ブロックの MIR をスキャンして、実際に使われる変数のみを返す
|
||||
///
|
||||
/// **精密化計画**:
|
||||
/// 1. LoopFormOps に `get_block_instructions()` 追加
|
||||
/// 2. exit ブロックの Copy/BinOp/Call 等で read される ValueId を収集
|
||||
/// 3. ValueId → 変数名の逆引き(variable_map 逆マップ)
|
||||
/// 4. read される変数のみを live_at_exit に含める
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// // Phase 1: 保守的近似
|
||||
/// // skip_whitespace: ch は一部 pred でしか定義されないが、exit 後で使われる
|
||||
/// let live_at_exit = liveness_box.compute_live_at_exit(
|
||||
/// query,
|
||||
/// exit_block,
|
||||
/// &header_vals, // { i: %10, n: %20 }
|
||||
/// &exit_snapshots, // [{ i: %30, ch: %40 }, { i: %50 }]
|
||||
/// );
|
||||
/// // → live_at_exit = { "i", "n", "ch" }(保守的に ch も含める)
|
||||
/// ```
|
||||
pub fn compute_live_at_exit(
|
||||
&self,
|
||||
_mir_query: &dyn crate::mir::MirQuery,
|
||||
_exit_block: BasicBlockId,
|
||||
_header_vals: &BTreeMap<String, ValueId>,
|
||||
_exit_snapshots: &[(BasicBlockId, BTreeMap<String, ValueId>)],
|
||||
) -> BTreeSet<String> {
|
||||
// Phase 26-F-4: 環境変数制御による段階的実装
|
||||
//
|
||||
// **環境変数ガード**:
|
||||
// - NYASH_EXIT_LIVE_ENABLE=1: Phase 2+ MIRスキャン実装を有効化(実験用)
|
||||
// - デフォルト(未設定): Phase 26-F-3 相当の挙動(安定版)
|
||||
|
||||
let enable_phase2 = std::env::var("NYASH_EXIT_LIVE_ENABLE").ok().as_deref() == Some("1");
|
||||
|
||||
let live_vars = if enable_phase2 {
|
||||
// Phase 2+ MIRスキャン実装(未実装)
|
||||
// TODO: LoopFormOps拡張後に実装
|
||||
// - exit ブロックの MIR をスキャン
|
||||
// - Copy/BinOp/Call 等で read される ValueId を収集
|
||||
// - ValueId → 変数名の逆引き
|
||||
// - read される変数のみを live_at_exit に含める
|
||||
BTreeSet::new()
|
||||
} else {
|
||||
// Phase 1: 空の live_at_exit を返す(Phase 26-F-3 相当)
|
||||
//
|
||||
// **理由**: 保守的近似(全変数を live)では以前の PhiInvariantsBox でエラーだった
|
||||
// Phase 35-5 で phi_invariants.rs 削除済み、検証は JoinIR Verifier へ移譲
|
||||
//
|
||||
// **問題**: header_vals + exit_snapshots の全変数を live とすると、
|
||||
// BodyLocalInternal 変数も live_at_exit に含まれる
|
||||
// → exit PHI 候補に昇格
|
||||
// → 一部の exit pred でのみ定義 → 構造的に不正
|
||||
//
|
||||
// **Phase 1 方針**: live_at_exit を空にして、既存の LoopVarClassBox 分類のみに依存
|
||||
// → BodyLocalInternal は exit PHI 候補から除外(Phase 26-F-3 と同じ)
|
||||
BTreeSet::new()
|
||||
};
|
||||
|
||||
// Debug trace
|
||||
if std::env::var("NYASH_EXIT_LIVENESS_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!(
|
||||
"[LoopExitLivenessBox] Phase {}: live_at_exit={} vars{}",
|
||||
if enable_phase2 { "2+" } else { "1" },
|
||||
live_vars.len(),
|
||||
if enable_phase2 {
|
||||
" (MIR scan - experimental)"
|
||||
} else {
|
||||
" (empty - Phase 26-F-3 equivalent)"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
live_vars
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Phase 30 F-1.2: LoopScopeShape 委譲メソッド
|
||||
// ========================================================================
|
||||
|
||||
/// Phase 30: LoopScopeShape の exit_live を直接取得
|
||||
///
|
||||
/// 新規コードではこのメソッドを使うことを推奨。
|
||||
/// 将来的には旧 compute_live_at_exit() メソッドを削除し、
|
||||
/// LoopScopeShape::exit_live を直接参照するのが標準になる。
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// let scope = LoopScopeShape::from_existing_boxes(...)?;
|
||||
/// let live = liveness_box.get_exit_live_from_scope(&scope);
|
||||
/// ```
|
||||
#[allow(dead_code)] // Phase 30 F-1.2: will be used when LoopExitLivenessBox is absorbed
|
||||
pub(crate) fn get_exit_live_from_scope(
|
||||
&self,
|
||||
scope: &crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
|
||||
) -> BTreeSet<String> {
|
||||
scope.exit_live.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExitLivenessProvider for LoopExitLivenessBox {
|
||||
fn compute_live_at_exit(
|
||||
&self,
|
||||
_mir_query: &dyn crate::mir::MirQuery,
|
||||
_exit_block: BasicBlockId,
|
||||
header_vals: &BTreeMap<String, ValueId>,
|
||||
exit_snapshots: &[(BasicBlockId, BTreeMap<String, ValueId>)],
|
||||
) -> BTreeSet<String> {
|
||||
self.compute_live_at_exit(_mir_query, _exit_block, header_vals, exit_snapshots)
|
||||
}
|
||||
}
|
||||
|
||||
/// MirScanExitLiveness - Phase 2+ 用のExitLivenessProviderプレースホルダ
|
||||
///
|
||||
/// Phase 26-F 時点では LoopExitLivenessBox と同じ実装を呼び出すだけの薄い箱だよ。
|
||||
/// 将来、LoopFormOps や MirFunction へのアクセスが整備されたら、
|
||||
/// ここに本物の MIR スキャン実装(use/def ベースの live_at_exit 計算)を差し込む予定。
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct MirScanExitLiveness;
|
||||
|
||||
impl ExitLivenessProvider for MirScanExitLiveness {
|
||||
fn compute_live_at_exit(
|
||||
&self,
|
||||
mir_query: &dyn crate::mir::MirQuery,
|
||||
exit_block: BasicBlockId,
|
||||
header_vals: &BTreeMap<String, ValueId>,
|
||||
exit_snapshots: &[(BasicBlockId, BTreeMap<String, ValueId>)],
|
||||
) -> BTreeSet<String> {
|
||||
let trace = std::env::var("NYASH_EXIT_LIVENESS_TRACE").ok().as_deref() == Some("1");
|
||||
|
||||
// 対象ブロック集合(exit と break preds)
|
||||
let mut targets: BTreeSet<BasicBlockId> = BTreeSet::new();
|
||||
targets.insert(exit_block);
|
||||
for (bb, _) in exit_snapshots {
|
||||
targets.insert(*bb);
|
||||
}
|
||||
|
||||
// live map per block
|
||||
let mut live_map: BTreeMap<BasicBlockId, BTreeSet<ValueId>> = BTreeMap::new();
|
||||
for bb in &targets {
|
||||
live_map.insert(*bb, BTreeSet::new());
|
||||
}
|
||||
|
||||
let mut changed = true;
|
||||
while changed {
|
||||
changed = false;
|
||||
// 逆順で走査(安定順序: BTreeSet)
|
||||
for bb in targets.iter().rev() {
|
||||
let mut live: BTreeSet<ValueId> = BTreeSet::new();
|
||||
// succ の live を流入(対象集合内のみ)
|
||||
for succ in mir_query.succs(*bb) {
|
||||
if targets.contains(&succ) {
|
||||
if let Some(succ_live) = live_map.get(&succ) {
|
||||
live.extend(succ_live);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 命令を逆順スキャン
|
||||
for inst in mir_query.insts_in_block(*bb).iter().rev() {
|
||||
// kill writes
|
||||
for w in mir_query.writes_of(inst) {
|
||||
live.remove(&w);
|
||||
}
|
||||
// add reads
|
||||
for r in mir_query.reads_of(inst) {
|
||||
live.insert(r);
|
||||
}
|
||||
}
|
||||
// 更新チェック
|
||||
let entry = live_map.entry(*bb).or_default();
|
||||
if *entry != live {
|
||||
*entry = live;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ValueId→名前の逆引きテーブル(header + exit snapshots)
|
||||
let mut name_pool: BTreeMap<ValueId, String> = BTreeMap::new();
|
||||
for (name, vid) in header_vals {
|
||||
name_pool.insert(*vid, name.clone());
|
||||
}
|
||||
for (_bb, snap) in exit_snapshots {
|
||||
for (name, vid) in snap {
|
||||
name_pool.insert(*vid, name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut live_names: BTreeSet<String> = BTreeSet::new();
|
||||
for live in live_map.values() {
|
||||
for v in live {
|
||||
if let Some(name) = name_pool.get(v) {
|
||||
live_names.insert(name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if trace {
|
||||
eprintln!(
|
||||
"[LoopExitLiveness/MirScan] live_at_exit={} vars (use/def scan)",
|
||||
live_names.len()
|
||||
);
|
||||
}
|
||||
|
||||
live_names
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Unit Tests
|
||||
// ============================================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
struct EmptyQuery;
|
||||
impl crate::mir::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_compute_live_at_exit_conservative() {
|
||||
let liveness_box = LoopExitLivenessBox::new();
|
||||
let query = EmptyQuery;
|
||||
|
||||
let mut header_vals = BTreeMap::new();
|
||||
header_vals.insert("i".to_string(), ValueId(10));
|
||||
header_vals.insert("n".to_string(), ValueId(20));
|
||||
|
||||
let mut snap1 = BTreeMap::new();
|
||||
snap1.insert("i".to_string(), ValueId(30));
|
||||
snap1.insert("ch".to_string(), ValueId(40));
|
||||
|
||||
let mut snap2 = BTreeMap::new();
|
||||
snap2.insert("i".to_string(), ValueId(50));
|
||||
snap2.insert("pname".to_string(), ValueId(60));
|
||||
|
||||
let exit_snapshots = vec![(BasicBlockId(100), snap1), (BasicBlockId(200), snap2)];
|
||||
|
||||
let live_at_exit = liveness_box.compute_live_at_exit(
|
||||
&query,
|
||||
BasicBlockId(0),
|
||||
&header_vals,
|
||||
&exit_snapshots,
|
||||
);
|
||||
|
||||
// Phase 1: 空の live_at_exit(MIRスキャン実装待ち)
|
||||
assert_eq!(live_at_exit.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compute_live_at_exit_empty() {
|
||||
let liveness_box = LoopExitLivenessBox::new();
|
||||
let query = EmptyQuery;
|
||||
|
||||
let header_vals = BTreeMap::new();
|
||||
let exit_snapshots = vec![];
|
||||
|
||||
let live_at_exit = liveness_box.compute_live_at_exit(
|
||||
&query,
|
||||
BasicBlockId(0),
|
||||
&header_vals,
|
||||
&exit_snapshots,
|
||||
);
|
||||
|
||||
assert_eq!(live_at_exit.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compute_live_at_exit_deduplication() {
|
||||
let liveness_box = LoopExitLivenessBox::new();
|
||||
let query = EmptyQuery;
|
||||
|
||||
let mut header_vals = BTreeMap::new();
|
||||
header_vals.insert("i".to_string(), ValueId(10));
|
||||
|
||||
let mut snap1 = BTreeMap::new();
|
||||
snap1.insert("i".to_string(), ValueId(20)); // 重複
|
||||
|
||||
let mut snap2 = BTreeMap::new();
|
||||
snap2.insert("i".to_string(), ValueId(30)); // 重複
|
||||
|
||||
let exit_snapshots = vec![(BasicBlockId(100), snap1), (BasicBlockId(200), snap2)];
|
||||
|
||||
let live_at_exit = liveness_box.compute_live_at_exit(
|
||||
&query,
|
||||
BasicBlockId(0),
|
||||
&header_vals,
|
||||
&exit_snapshots,
|
||||
);
|
||||
|
||||
// Phase 1: 空の live_at_exit(MIRスキャン実装待ち)
|
||||
assert_eq!(live_at_exit.len(), 0);
|
||||
}
|
||||
}
|
||||
@ -1,40 +1,15 @@
|
||||
/*!
|
||||
* Phase 36: LoopSnapshotMergeBox - Exit PHI Merge Utility (Pure Static)
|
||||
* LoopSnapshotMergeBox - Exit PHI merge utility (pinned/carrier + availability)
|
||||
*
|
||||
* **Responsibility**: Exit PHI input merging with variable classification (Option C)
|
||||
*
|
||||
* ## What This Box Does (Phase 36 Scope)
|
||||
* - Exit PHI input merging with LoopVarClassBox classification
|
||||
* - PHI pred mismatch prevention via LocalScopeInspectorBox
|
||||
* - Header fallthrough + break snapshot merging
|
||||
*
|
||||
* ## What This Box Does NOT Do (Migrated to Other Systems)
|
||||
* - Header PHI generation: → LoopFormBuilder::seal_phis() + PhiInputCollector
|
||||
* - PHI optimization: → PhiInputCollector::optimize_same_value()
|
||||
* - PHI sanitization: → PhiInputCollector::sanitize()
|
||||
*
|
||||
* ## Future Migration (Phase 37+)
|
||||
* - Variable classification: Consider moving to LoopScopeShape
|
||||
* - Exit PHI generation: Consider JoinIR Exit lowering coverage expansion
|
||||
*
|
||||
* ## Design: Pure Static Utility (No State)
|
||||
* This struct has no fields and provides only static methods.
|
||||
* It acts as a namespace for exit PHI merging logic.
|
||||
*
|
||||
* ## History
|
||||
* - Phase 25.2: Created for snapshot merge unification (~210 line reduction)
|
||||
* - Phase 36: Removed dead code (merge_continue_for_header), superseded helpers
|
||||
* - Reduced from 470 → 309 lines (34% reduction)
|
||||
* - Converted to pure static utility (no state)
|
||||
* - Exit PHI inputs are merged using pinned/carrier hints and availability
|
||||
* reconstructed from header/exit snapshots (Option C guard)。
|
||||
* - Header/exit PHI の仕様や最適化は LoopFormBuilder / LoopScopeShape 側が SSOT。
|
||||
* - 将来の縮退・移行計画は docs 側(Phase 37+ メモ)を参照してね。
|
||||
*/
|
||||
|
||||
use crate::mir::{BasicBlockId, ValueId};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
// Option C PHI bug fix: Use box-based classification
|
||||
use super::local_scope_inspector::LocalScopeInspectorBox;
|
||||
use super::loop_var_classifier::LoopVarClassBox;
|
||||
|
||||
/// Phase 36: Pure static utility for exit PHI merging (no state)
|
||||
///
|
||||
/// # Responsibility
|
||||
@ -54,16 +29,10 @@ impl LoopSnapshotMergeBox {
|
||||
/// # Phase 36 Essential Logic
|
||||
///
|
||||
/// This method is the SSOT for exit PHI input merging with:
|
||||
/// 1. LoopVarClassBox classification (Pinned/Carrier/BodyLocalInOut/BodyLocalInternal)
|
||||
/// 2. LocalScopeInspectorBox availability checking
|
||||
/// 1. 変数分類(Pinned/Carrier/BodyLocalExit/BodyLocalInternal)
|
||||
/// 2. スナップショットに基づく availability チェック
|
||||
/// 3. PHI pred mismatch prevention (Option C)
|
||||
///
|
||||
/// # Why This Can't Be in LoopScopeShape (Yet)
|
||||
///
|
||||
/// - Variable classification needs LoopVarClassBox + LocalScopeInspectorBox
|
||||
/// - Option C logic is complex and battle-tested
|
||||
/// - JoinIR Exit lowering doesn't cover all patterns yet (Phase 34 partial)
|
||||
///
|
||||
/// # Future Migration Path (Phase 37+)
|
||||
///
|
||||
/// - Expand JoinIR Exit lowering to cover complex patterns
|
||||
@ -90,8 +59,8 @@ impl LoopSnapshotMergeBox {
|
||||
///
|
||||
/// ## Option C ロジック
|
||||
///
|
||||
/// 1. LocalScopeInspectorBox で変数定義位置を追跡
|
||||
/// 2. LoopVarClassBox で変数を分類
|
||||
/// 1. スナップショットから変数定義位置を再構築
|
||||
/// 2. pinned/carrier hint を優先し、残りは availability で BodyLocalExit/BodyLocalInternal を判定
|
||||
/// 3. BodyLocalInternal(全exit predsで定義されていない)変数は skip
|
||||
///
|
||||
/// ## 例(skip_whitespace バグ修正)
|
||||
@ -116,18 +85,7 @@ impl LoopSnapshotMergeBox {
|
||||
|
||||
let debug = std::env::var("NYASH_OPTION_C_DEBUG").is_ok();
|
||||
|
||||
// Phase 69-2: inspector を内部で構築(外部引数から削除)
|
||||
// exit_snapshots から変数定義位置を再構築
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
for (block_id, snapshot) in exit_snapshots {
|
||||
for var_name in snapshot.keys() {
|
||||
inspector.record_definition(var_name, *block_id);
|
||||
}
|
||||
}
|
||||
// header_vals も記録
|
||||
for var_name in header_vals.keys() {
|
||||
inspector.record_definition(var_name, header_id);
|
||||
}
|
||||
let definitions = build_definitions(header_id, header_vals, exit_snapshots);
|
||||
if debug {
|
||||
eprintln!("[Option C] merge_exit_with_classification called");
|
||||
eprintln!("[Option C] exit_preds: {:?}", exit_preds);
|
||||
@ -144,9 +102,6 @@ impl LoopSnapshotMergeBox {
|
||||
// header 値を exit PHI 入力に含めると「非支配ブロックからの値参照」で壊れる。
|
||||
let header_in_exit_preds = exit_preds.contains(&header_id);
|
||||
|
||||
// LoopVarClassBox でフィルタリング
|
||||
let classifier = LoopVarClassBox::new();
|
||||
|
||||
// すべての変数名を収集(決定的順序のためBTreeSet使用)
|
||||
let mut all_vars: BTreeSet<String> = BTreeSet::new();
|
||||
all_vars.extend(header_vals.keys().cloned());
|
||||
@ -156,34 +111,35 @@ impl LoopSnapshotMergeBox {
|
||||
|
||||
// 各変数を分類して、exit PHI が必要なもののみ処理(アルファベット順で決定的)
|
||||
for var_name in all_vars {
|
||||
// Option C: 変数分類(実際のCFG predecessorsを使用)
|
||||
let class = classifier.classify(
|
||||
let class = classify_exit_var(
|
||||
&var_name,
|
||||
pinned_vars,
|
||||
carrier_vars,
|
||||
&inspector,
|
||||
exit_preds, // ← 実際のCFG predecessorsを使用!
|
||||
exit_preds,
|
||||
&definitions,
|
||||
);
|
||||
let needs_exit_phi = class_needs_exit_phi(class);
|
||||
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[Option C] var '{}': {:?} needs_exit_phi={}",
|
||||
var_name,
|
||||
class,
|
||||
class.needs_exit_phi()
|
||||
needs_exit_phi
|
||||
);
|
||||
let defining_blocks = inspector.get_defining_blocks(&var_name);
|
||||
eprintln!("[Option C] defining_blocks: {:?}", defining_blocks);
|
||||
if let Some(defining_blocks) = definitions.get(&var_name) {
|
||||
eprintln!("[Option C] defining_blocks: {:?}", defining_blocks);
|
||||
}
|
||||
}
|
||||
|
||||
// Option C: Additional check - even Carrier/Pinned need definition check!
|
||||
// Carrier/Pinned だからといって、全 exit preds で定義されているとは限らない
|
||||
let is_in_all_preds = inspector.is_available_in_all(&var_name, exit_preds);
|
||||
let is_in_all_preds = is_available_in_all(&var_name, exit_preds, &definitions);
|
||||
|
||||
// exit PHI が不要な場合は skip
|
||||
if !class.needs_exit_phi() || !is_in_all_preds {
|
||||
if !needs_exit_phi || !is_in_all_preds {
|
||||
if debug {
|
||||
if !class.needs_exit_phi() {
|
||||
if !needs_exit_phi {
|
||||
eprintln!(
|
||||
"[Option C] → SKIP exit PHI for '{}' (class={:?})",
|
||||
var_name, class
|
||||
@ -240,6 +196,88 @@ impl LoopSnapshotMergeBox {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum ExitVarClass {
|
||||
Pinned,
|
||||
Carrier,
|
||||
BodyLocalExit,
|
||||
BodyLocalInternal,
|
||||
}
|
||||
|
||||
fn classify_exit_var(
|
||||
var_name: &str,
|
||||
pinned_vars: &[String],
|
||||
carrier_vars: &[String],
|
||||
exit_preds: &[BasicBlockId],
|
||||
definitions: &BTreeMap<String, BTreeSet<BasicBlockId>>,
|
||||
) -> ExitVarClass {
|
||||
if is_pin_temp(var_name) {
|
||||
return ExitVarClass::BodyLocalInternal;
|
||||
}
|
||||
|
||||
if pinned_vars.iter().any(|p| p == var_name) {
|
||||
return ExitVarClass::Pinned;
|
||||
}
|
||||
|
||||
if carrier_vars.iter().any(|c| c == var_name) {
|
||||
return ExitVarClass::Carrier;
|
||||
}
|
||||
|
||||
if is_available_in_all(var_name, exit_preds, definitions) {
|
||||
ExitVarClass::BodyLocalExit
|
||||
} else {
|
||||
ExitVarClass::BodyLocalInternal
|
||||
}
|
||||
}
|
||||
|
||||
fn class_needs_exit_phi(class: ExitVarClass) -> bool {
|
||||
matches!(
|
||||
class,
|
||||
ExitVarClass::Pinned | ExitVarClass::Carrier | ExitVarClass::BodyLocalExit
|
||||
)
|
||||
}
|
||||
|
||||
fn build_definitions(
|
||||
header_id: BasicBlockId,
|
||||
header_vals: &BTreeMap<String, ValueId>,
|
||||
exit_snapshots: &[(BasicBlockId, BTreeMap<String, ValueId>)],
|
||||
) -> BTreeMap<String, BTreeSet<BasicBlockId>> {
|
||||
let mut definitions: BTreeMap<String, BTreeSet<BasicBlockId>> = BTreeMap::new();
|
||||
|
||||
for name in header_vals.keys() {
|
||||
definitions
|
||||
.entry(name.clone())
|
||||
.or_default()
|
||||
.insert(header_id);
|
||||
}
|
||||
|
||||
for (bb, snap) in exit_snapshots {
|
||||
for name in snap.keys() {
|
||||
definitions.entry(name.clone()).or_default().insert(*bb);
|
||||
}
|
||||
}
|
||||
|
||||
definitions
|
||||
}
|
||||
|
||||
fn is_available_in_all(
|
||||
var_name: &str,
|
||||
required_blocks: &[BasicBlockId],
|
||||
definitions: &BTreeMap<String, BTreeSet<BasicBlockId>>,
|
||||
) -> bool {
|
||||
if let Some(defining_blocks) = definitions.get(var_name) {
|
||||
required_blocks
|
||||
.iter()
|
||||
.all(|block| defining_blocks.contains(block))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn is_pin_temp(var_name: &str) -> bool {
|
||||
var_name.starts_with("__pin$") && var_name.contains("$@")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -293,7 +331,7 @@ mod tests {
|
||||
|
||||
let header_vals = BTreeMap::new(); // ch not in header
|
||||
|
||||
let mut break1_snap = BTreeMap::new();
|
||||
let break1_snap = BTreeMap::new();
|
||||
// break1: ch not defined yet (early exit)
|
||||
let mut break2_snap = BTreeMap::new();
|
||||
break2_snap.insert("ch".to_string(), ValueId::new(20)); // ch defined in break2
|
||||
|
||||
@ -1,578 +0,0 @@
|
||||
/// LoopVarClassBox - Loop variable classifier
|
||||
///
|
||||
/// # Purpose
|
||||
///
|
||||
/// This box classifies loop variables into 4 categories:
|
||||
/// - Pinned: Loop-crossing parameters (always need PHI)
|
||||
/// - Carrier: Loop-modified variables (always need PHI)
|
||||
/// - BodyLocalExit: Body-local but defined in ALL exit predecessors (need exit PHI)
|
||||
/// - BodyLocalInternal: Body-local, NOT in all exit predecessors (NO exit PHI)
|
||||
///
|
||||
/// # Responsibility
|
||||
///
|
||||
/// - Classify variables based on their definition scope
|
||||
/// - Use LocalScopeInspectorBox to check definition locations
|
||||
/// - Provide classification for PHI generation decision
|
||||
///
|
||||
/// # Design Philosophy (Box Theory)
|
||||
///
|
||||
/// This box has ONE job: classify variables.
|
||||
/// It doesn't generate PHI nodes or modify IR.
|
||||
/// It's a pure decision box that other boxes can query.
|
||||
use super::local_scope_inspector::LocalScopeInspectorBox;
|
||||
use crate::mir::BasicBlockId;
|
||||
|
||||
/// Variable classification for loop PHI generation
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LoopVarClass {
|
||||
/// Loop-crossing parameter (always needs header/exit PHI)
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// method process(limit) { // limit is Pinned
|
||||
/// local i = 0
|
||||
/// loop(i < limit) { i = i + 1 } // limit doesn't change
|
||||
/// }
|
||||
/// ```
|
||||
Pinned,
|
||||
|
||||
/// Loop-modified variable (always needs header/exit PHI)
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// local i = 0
|
||||
/// loop(i < 10) {
|
||||
/// i = i + 1 // i is Carrier (modified in loop)
|
||||
/// }
|
||||
/// ```
|
||||
Carrier,
|
||||
|
||||
/// Body-local variable defined in ALL exit predecessors (needs exit PHI)
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// loop(condition) {
|
||||
/// local x = compute() // defined at loop entry
|
||||
/// if x > 10 { break } // exit path 1: x exists
|
||||
/// if x < 0 { break } // exit path 2: x exists
|
||||
/// }
|
||||
/// // x is BodyLocalExit → needs exit PHI
|
||||
/// ```
|
||||
BodyLocalExit,
|
||||
|
||||
/// Body-local variable NOT in all exit predecessors (NO exit PHI)
|
||||
///
|
||||
/// Example (skip_whitespace bug):
|
||||
/// ```
|
||||
/// loop(1 == 1) {
|
||||
/// if i >= n { break } // exit path 1: ch doesn't exist
|
||||
/// local ch = s.substring() // ch defined here
|
||||
/// if ch == " " { ... } else { break } // exit path 2: ch exists
|
||||
/// }
|
||||
/// // ch is BodyLocalInternal → NO exit PHI (would cause pred mismatch!)
|
||||
/// ```
|
||||
BodyLocalInternal,
|
||||
}
|
||||
|
||||
impl LoopVarClass {
|
||||
/// Check if this classification requires exit PHI
|
||||
pub fn needs_exit_phi(self) -> bool {
|
||||
match self {
|
||||
LoopVarClass::Pinned => true,
|
||||
LoopVarClass::Carrier => true,
|
||||
LoopVarClass::BodyLocalExit => true,
|
||||
LoopVarClass::BodyLocalInternal => false, // ← Option C: Skip exit PHI!
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if this classification requires header PHI
|
||||
pub fn needs_header_phi(self) -> bool {
|
||||
match self {
|
||||
LoopVarClass::Pinned => true,
|
||||
LoopVarClass::Carrier => true,
|
||||
LoopVarClass::BodyLocalExit => false, // Body-local: no header PHI
|
||||
LoopVarClass::BodyLocalInternal => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get human-readable description
|
||||
pub fn description(&self) -> &'static str {
|
||||
match self {
|
||||
LoopVarClass::Pinned => "Loop-crossing parameter",
|
||||
LoopVarClass::Carrier => "Loop-modified variable",
|
||||
LoopVarClass::BodyLocalExit => "Body-local (all exits)",
|
||||
LoopVarClass::BodyLocalInternal => "Body-local (partial exits)",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Loop variable classifier box
|
||||
///
|
||||
/// # Phase 30: LoopScopeShape 移行中
|
||||
///
|
||||
/// このBoxは将来 LoopScopeShape に吸収される予定。
|
||||
/// 新規コードは `LoopScopeShape::classify()` を直接使うことを推奨。
|
||||
///
|
||||
/// # Usage (Legacy)
|
||||
///
|
||||
/// ```
|
||||
/// let inspector = LocalScopeInspectorBox::new();
|
||||
/// // ... record definitions ...
|
||||
///
|
||||
/// let classifier = LoopVarClassBox::new();
|
||||
/// let class = classifier.classify(
|
||||
/// "ch",
|
||||
/// &["limit", "n"], // pinned_vars
|
||||
/// &["i"], // carrier_vars
|
||||
/// &inspector,
|
||||
/// &exit_preds,
|
||||
/// );
|
||||
///
|
||||
/// if class.needs_exit_phi() {
|
||||
/// // Generate exit PHI
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Usage (Phase 30 Recommended)
|
||||
///
|
||||
/// ```
|
||||
/// // LoopScopeShape が利用可能な場合は直接使う
|
||||
/// let class = scope.classify("ch");
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct LoopVarClassBox;
|
||||
|
||||
impl LoopVarClassBox {
|
||||
/// Create a new classifier
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// Classify a variable for PHI generation decision
|
||||
///
|
||||
/// # Phase 30 TODO
|
||||
///
|
||||
/// このメソッドは将来 `LoopScopeShape::classify()` に置き換える予定。
|
||||
/// 呼び出し側が LoopScopeShape を持っている場合は、そちらを直接使うこと。
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `var_name`: Variable to classify
|
||||
/// - `pinned_vars`: Known loop-crossing parameters
|
||||
/// - `carrier_vars`: Known loop-modified variables
|
||||
/// - `inspector`: LocalScopeInspectorBox to check definition locations
|
||||
/// - `exit_preds`: All exit predecessor blocks
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// LoopVarClass indicating PHI generation requirements
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// // skip_whitespace scenario:
|
||||
/// let class = classifier.classify(
|
||||
/// "ch",
|
||||
/// &[], // not pinned
|
||||
/// &[], // not carrier
|
||||
/// &inspector, // ch only in block 5
|
||||
/// &[BasicBlockId(2), BasicBlockId(5)], // exit preds
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(class, LoopVarClass::BodyLocalInternal);
|
||||
/// assert!(!class.needs_exit_phi()); // ← Skip exit PHI!
|
||||
/// ```
|
||||
pub fn classify(
|
||||
&self,
|
||||
var_name: &str,
|
||||
pinned_vars: &[String],
|
||||
carrier_vars: &[String],
|
||||
inspector: &LocalScopeInspectorBox,
|
||||
exit_preds: &[BasicBlockId],
|
||||
) -> LoopVarClass {
|
||||
// Priority 0: __pin$ temporary variables are ALWAYS BodyLocalInternal
|
||||
// Reason: These are compiler-generated temporaries for pinning expression values.
|
||||
// They are defined inside loop bodies and should NEVER get exit PHIs.
|
||||
// Task先生の発見: ValueId(289)等の未定義値エラーの根本原因!
|
||||
if var_name.starts_with("__pin$") && var_name.contains("$@") {
|
||||
return LoopVarClass::BodyLocalInternal;
|
||||
}
|
||||
|
||||
// Priority 1: Check if it's a pinned variable
|
||||
if pinned_vars.iter().any(|p| p == var_name) {
|
||||
return LoopVarClass::Pinned;
|
||||
}
|
||||
|
||||
// Priority 2: Check if it's a carrier variable
|
||||
if carrier_vars.iter().any(|c| c == var_name) {
|
||||
return LoopVarClass::Carrier;
|
||||
}
|
||||
|
||||
// Priority 3: Check if it's a body-local variable
|
||||
// Option C logic: Check if defined in ALL exit predecessors
|
||||
if inspector.is_available_in_all(var_name, exit_preds) {
|
||||
LoopVarClass::BodyLocalExit
|
||||
} else {
|
||||
// ← Option C: Skip exit PHI for partial body-local variables!
|
||||
LoopVarClass::BodyLocalInternal
|
||||
}
|
||||
}
|
||||
|
||||
/// Batch classify multiple variables
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Vec of (var_name, classification) tuples
|
||||
pub fn classify_all(
|
||||
&self,
|
||||
var_names: &[String],
|
||||
pinned_vars: &[String],
|
||||
carrier_vars: &[String],
|
||||
inspector: &LocalScopeInspectorBox,
|
||||
exit_preds: &[BasicBlockId],
|
||||
) -> Vec<(String, LoopVarClass)> {
|
||||
var_names
|
||||
.iter()
|
||||
.map(|name| {
|
||||
let class = self.classify(name, pinned_vars, carrier_vars, inspector, exit_preds);
|
||||
(name.clone(), class)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Filter variables that need exit PHI
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Vec of variable names that should get exit PHI
|
||||
pub fn filter_exit_phi_candidates(
|
||||
&self,
|
||||
var_names: &[String],
|
||||
pinned_vars: &[String],
|
||||
carrier_vars: &[String],
|
||||
inspector: &LocalScopeInspectorBox,
|
||||
exit_preds: &[BasicBlockId],
|
||||
) -> Vec<String> {
|
||||
var_names
|
||||
.iter()
|
||||
.filter(|name| {
|
||||
let class = self.classify(name, pinned_vars, carrier_vars, inspector, exit_preds);
|
||||
class.needs_exit_phi()
|
||||
})
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Phase 30 F-1.1: LoopScopeShape 委譲メソッド
|
||||
// ========================================================================
|
||||
|
||||
/// Phase 30: LoopScopeShape を使って変数を分類
|
||||
///
|
||||
/// 新規コードではこのメソッドを使うことを推奨。
|
||||
/// 将来的には旧 classify() メソッドを削除し、このメソッドが標準になる。
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// let scope = LoopScopeShape::from_existing_boxes(...)?;
|
||||
/// let class = classifier.classify_with_scope("ch", &scope);
|
||||
/// ```
|
||||
#[allow(dead_code)] // Phase 30 F-1.1: will be used when LoopVarClassBox is absorbed
|
||||
pub(crate) fn classify_with_scope(
|
||||
&self,
|
||||
var_name: &str,
|
||||
scope: &crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
|
||||
) -> LoopVarClass {
|
||||
scope.classify(var_name)
|
||||
}
|
||||
|
||||
/// Phase 30: LoopScopeShape を使って複数変数を一括分類
|
||||
#[allow(dead_code)] // Phase 30 F-1.1: will be used when LoopVarClassBox is absorbed
|
||||
pub(crate) fn classify_all_with_scope(
|
||||
&self,
|
||||
var_names: &[String],
|
||||
scope: &crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
|
||||
) -> Vec<(String, LoopVarClass)> {
|
||||
scope.classify_all(var_names)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Unit Tests
|
||||
// ============================================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_loop_var_class_needs_exit_phi() {
|
||||
assert!(LoopVarClass::Pinned.needs_exit_phi());
|
||||
assert!(LoopVarClass::Carrier.needs_exit_phi());
|
||||
assert!(LoopVarClass::BodyLocalExit.needs_exit_phi());
|
||||
assert!(!LoopVarClass::BodyLocalInternal.needs_exit_phi()); // ← Option C!
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_loop_var_class_needs_header_phi() {
|
||||
assert!(LoopVarClass::Pinned.needs_header_phi());
|
||||
assert!(LoopVarClass::Carrier.needs_header_phi());
|
||||
assert!(!LoopVarClass::BodyLocalExit.needs_header_phi());
|
||||
assert!(!LoopVarClass::BodyLocalInternal.needs_header_phi());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_classify_pinned_variable() {
|
||||
let inspector = LocalScopeInspectorBox::new();
|
||||
let classifier = LoopVarClassBox::new();
|
||||
|
||||
let class = classifier.classify(
|
||||
"limit",
|
||||
&["limit".to_string()], // pinned
|
||||
&[],
|
||||
&inspector,
|
||||
&[],
|
||||
);
|
||||
|
||||
assert_eq!(class, LoopVarClass::Pinned);
|
||||
assert!(class.needs_exit_phi());
|
||||
assert!(class.needs_header_phi());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_classify_carrier_variable() {
|
||||
let inspector = LocalScopeInspectorBox::new();
|
||||
let classifier = LoopVarClassBox::new();
|
||||
|
||||
let class = classifier.classify(
|
||||
"i",
|
||||
&[],
|
||||
&["i".to_string()], // carrier
|
||||
&inspector,
|
||||
&[],
|
||||
);
|
||||
|
||||
assert_eq!(class, LoopVarClass::Carrier);
|
||||
assert!(class.needs_exit_phi());
|
||||
assert!(class.needs_header_phi());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_classify_body_local_exit() {
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
let classifier = LoopVarClassBox::new();
|
||||
|
||||
// Variable "x" is defined in all exit predecessors
|
||||
let block_2 = BasicBlockId::new(2);
|
||||
let block_5 = BasicBlockId::new(5);
|
||||
|
||||
inspector.record_definition("x", block_2);
|
||||
inspector.record_definition("x", block_5);
|
||||
|
||||
let exit_preds = vec![block_2, block_5];
|
||||
|
||||
let class = classifier.classify("x", &[], &[], &inspector, &exit_preds);
|
||||
|
||||
assert_eq!(class, LoopVarClass::BodyLocalExit);
|
||||
assert!(class.needs_exit_phi()); // Should get exit PHI
|
||||
assert!(!class.needs_header_phi());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_classify_body_local_internal() {
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
let classifier = LoopVarClassBox::new();
|
||||
|
||||
// Variable "ch" is only defined in block 5, not in block 2
|
||||
let block_2 = BasicBlockId::new(2);
|
||||
let block_5 = BasicBlockId::new(5);
|
||||
|
||||
inspector.record_definition("ch", block_5); // Only block 5!
|
||||
|
||||
let exit_preds = vec![block_2, block_5];
|
||||
|
||||
let class = classifier.classify("ch", &[], &[], &inspector, &exit_preds);
|
||||
|
||||
assert_eq!(class, LoopVarClass::BodyLocalInternal);
|
||||
assert!(!class.needs_exit_phi()); // ← Option C: Skip exit PHI!
|
||||
assert!(!class.needs_header_phi());
|
||||
}
|
||||
|
||||
/// Test the skip_whitespace PHI bug scenario
|
||||
#[test]
|
||||
fn test_skip_whitespace_scenario() {
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
let classifier = LoopVarClassBox::new();
|
||||
|
||||
// Simulate skip_whitespace loop:
|
||||
// - Variables i, n, s are pinned/carrier (defined everywhere)
|
||||
// - Variable ch is only defined in block 5 (break path 2)
|
||||
|
||||
let block_2 = BasicBlockId::new(2); // header / break path 1
|
||||
let block_5 = BasicBlockId::new(5); // break path 2
|
||||
let block_7 = BasicBlockId::new(7); // latch (hypothetical)
|
||||
|
||||
// i, n, s are in all blocks
|
||||
for var in &["i", "n", "s"] {
|
||||
inspector.record_definition(var, block_2);
|
||||
inspector.record_definition(var, block_5);
|
||||
inspector.record_definition(var, block_7);
|
||||
}
|
||||
|
||||
// ch is only in block 5
|
||||
inspector.record_definition("ch", block_5);
|
||||
|
||||
let exit_preds = vec![block_2, block_5, block_7];
|
||||
|
||||
// Classify all variables
|
||||
let pinned = vec!["n".to_string(), "s".to_string()];
|
||||
let carrier = vec!["i".to_string()];
|
||||
|
||||
let i_class = classifier.classify("i", &pinned, &carrier, &inspector, &exit_preds);
|
||||
let n_class = classifier.classify("n", &pinned, &carrier, &inspector, &exit_preds);
|
||||
let s_class = classifier.classify("s", &pinned, &carrier, &inspector, &exit_preds);
|
||||
let ch_class = classifier.classify("ch", &pinned, &carrier, &inspector, &exit_preds);
|
||||
|
||||
// i, n, s should need exit PHI
|
||||
assert_eq!(i_class, LoopVarClass::Carrier);
|
||||
assert_eq!(n_class, LoopVarClass::Pinned);
|
||||
assert_eq!(s_class, LoopVarClass::Pinned);
|
||||
|
||||
assert!(i_class.needs_exit_phi());
|
||||
assert!(n_class.needs_exit_phi());
|
||||
assert!(s_class.needs_exit_phi());
|
||||
|
||||
// ch should NOT need exit PHI (Option C: prevents PHI pred mismatch!)
|
||||
assert_eq!(ch_class, LoopVarClass::BodyLocalInternal);
|
||||
assert!(!ch_class.needs_exit_phi()); // ← This fixes the bug!
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_classify_all() {
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
let classifier = LoopVarClassBox::new();
|
||||
|
||||
let block_2 = BasicBlockId::new(2);
|
||||
let block_5 = BasicBlockId::new(5);
|
||||
|
||||
// Setup: i, n in all blocks; ch only in block 5
|
||||
for var in &["i", "n"] {
|
||||
inspector.record_definition(var, block_2);
|
||||
inspector.record_definition(var, block_5);
|
||||
}
|
||||
inspector.record_definition("ch", block_5);
|
||||
|
||||
let vars = vec!["i".to_string(), "n".to_string(), "ch".to_string()];
|
||||
let pinned = vec!["n".to_string()];
|
||||
let carrier = vec!["i".to_string()];
|
||||
let exit_preds = vec![block_2, block_5];
|
||||
|
||||
let results = classifier.classify_all(&vars, &pinned, &carrier, &inspector, &exit_preds);
|
||||
|
||||
assert_eq!(results.len(), 3);
|
||||
assert_eq!(results[0], ("i".to_string(), LoopVarClass::Carrier));
|
||||
assert_eq!(results[1], ("n".to_string(), LoopVarClass::Pinned));
|
||||
assert_eq!(
|
||||
results[2],
|
||||
("ch".to_string(), LoopVarClass::BodyLocalInternal)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filter_exit_phi_candidates() {
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
let classifier = LoopVarClassBox::new();
|
||||
|
||||
let block_2 = BasicBlockId::new(2);
|
||||
let block_5 = BasicBlockId::new(5);
|
||||
|
||||
// Setup: i, n in all blocks; ch only in block 5
|
||||
for var in &["i", "n"] {
|
||||
inspector.record_definition(var, block_2);
|
||||
inspector.record_definition(var, block_5);
|
||||
}
|
||||
inspector.record_definition("ch", block_5);
|
||||
|
||||
let vars = vec!["i".to_string(), "n".to_string(), "ch".to_string()];
|
||||
let pinned = vec!["n".to_string()];
|
||||
let carrier = vec!["i".to_string()];
|
||||
let exit_preds = vec![block_2, block_5];
|
||||
|
||||
let candidates = classifier.filter_exit_phi_candidates(
|
||||
&vars,
|
||||
&pinned,
|
||||
&carrier,
|
||||
&inspector,
|
||||
&exit_preds,
|
||||
);
|
||||
|
||||
assert_eq!(candidates.len(), 2);
|
||||
assert!(candidates.contains(&"i".to_string()));
|
||||
assert!(candidates.contains(&"n".to_string()));
|
||||
assert!(!candidates.contains(&"ch".to_string())); // ← Option C: ch is filtered out!
|
||||
}
|
||||
|
||||
/// Test Task先生の発見: __pin$ temporary variables should be BodyLocalInternal
|
||||
#[test]
|
||||
fn test_classify_pin_temporary_variables() {
|
||||
let inspector = LocalScopeInspectorBox::new();
|
||||
let classifier = LoopVarClassBox::new();
|
||||
|
||||
// __pin$ temporary variables should ALWAYS be BodyLocalInternal
|
||||
// regardless of their definition locations
|
||||
let pin_vars = vec![
|
||||
"__pin$285$@binop_lhs",
|
||||
"__pin$286$@binop_rhs",
|
||||
"__pin$437$@binop_lhs",
|
||||
"__pin$297$@assign",
|
||||
];
|
||||
|
||||
for pin_var in pin_vars {
|
||||
let class = classifier.classify(
|
||||
pin_var,
|
||||
&[], // Not pinned
|
||||
&[], // Not carrier
|
||||
&inspector,
|
||||
&[], // No exit preds
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
class,
|
||||
LoopVarClass::BodyLocalInternal,
|
||||
"Variable '{}' should be BodyLocalInternal",
|
||||
pin_var
|
||||
);
|
||||
assert!(
|
||||
!class.needs_exit_phi(),
|
||||
"Variable '{}' should NOT need exit PHI",
|
||||
pin_var
|
||||
);
|
||||
assert!(
|
||||
!class.needs_header_phi(),
|
||||
"Variable '{}' should NOT need header PHI",
|
||||
pin_var
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_description() {
|
||||
assert_eq!(
|
||||
LoopVarClass::Pinned.description(),
|
||||
"Loop-crossing parameter"
|
||||
);
|
||||
assert_eq!(
|
||||
LoopVarClass::Carrier.description(),
|
||||
"Loop-modified variable"
|
||||
);
|
||||
assert_eq!(
|
||||
LoopVarClass::BodyLocalExit.description(),
|
||||
"Body-local (all exits)"
|
||||
);
|
||||
assert_eq!(
|
||||
LoopVarClass::BodyLocalInternal.description(),
|
||||
"Body-local (partial exits)"
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -13,35 +13,7 @@ pub mod if_phi;
|
||||
// Phase 30 F-2.1: loop_phi 削除(LoopFormBuilder が SSOT)
|
||||
pub mod loop_snapshot_merge;
|
||||
pub mod loopform_builder;
|
||||
|
||||
// Phase 69-4.2: Trio 公開面削減方針
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
// ⚠️ Trio Legacy Boxes (Phase 70 削除予定):
|
||||
// - LocalScopeInspectorBox (361行) - 変数定義位置追跡(LoopScopeShapeで代替済み)
|
||||
// - LoopVarClassBox (578行) - 変数分類(LoopScopeShapeで代替済み)
|
||||
// - LoopExitLivenessBox (414行) - Exit後生存変数分析(LoopScopeShapeで代替済み)
|
||||
//
|
||||
// 現在の外部依存(Phase 69-4.1棚卸し済み):
|
||||
// 1. src/mir/join_ir/lowering/loop_form_intake.rs (~30行) - LoopScopeShape移行待ち
|
||||
// 2. src/mir/phi_core/loop_snapshot_merge.rs (~60行) - Exit PHI生成で使用中
|
||||
//
|
||||
// Phase 69-4.2 方針:
|
||||
// - ✅ pub 公開継続(外部依存2箇所が残存)
|
||||
// - 🎯 目標: phi_core 内部+テストのみが知る状態(現在達成できず)
|
||||
// - 📋 Phase 70 実装時: json_v0_bridge 移行後に完全削除
|
||||
//
|
||||
// TODO(Phase 70): json_v0_bridge の LoopScopeShape 移行完了後、以下を削除:
|
||||
// - pub mod local_scope_inspector; (361行)
|
||||
// - pub mod loop_var_classifier; (578行)
|
||||
// - pub mod loop_exit_liveness; (414行)
|
||||
// - loop_snapshot_merge.rs 内の Trio 使用箇所 (~60行)
|
||||
// - loop_form_intake.rs 内の Trio 使用箇所 (~30行)
|
||||
// 合計削減見込み: ~1,443行
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
// Option C PHI bug fix: Box-based design (Phase 70 削除予定)
|
||||
pub mod local_scope_inspector;
|
||||
pub mod loop_var_classifier;
|
||||
// Trio legacy boxes removed in Phase 70: LoopScopeShape now owns classification/liveness.
|
||||
|
||||
// Phase 26-B: Box-First Refactoring
|
||||
// Phase 30 F-2.1: body_local_phi_builder 削除(LoopScopeShape で代替)
|
||||
@ -60,12 +32,6 @@ pub mod phi_builder_box;
|
||||
// Phase 35-5: if_body_local_merge 削除(PhiBuilderBoxに吸収済み)
|
||||
// Phase 35-5: phi_invariants 削除(JoinIR Verifierに移譲済み)
|
||||
|
||||
// Phase 26-F-4: Loop Exit Liveness Box - exit後で使われる変数決定箱
|
||||
// ⚠️ Phase 69-4.2: Trio Legacy Box (Phase 70 削除予定)
|
||||
// - 現在の外部依存: loop_form_intake.rs が使用中
|
||||
// - TODO(Phase 70): LoopScopeShape 移行後に削除
|
||||
pub mod loop_exit_liveness;
|
||||
|
||||
// Phase 61-7.0: Dead code 削除
|
||||
// 削除された facade 関数:
|
||||
// - build_if_phis(): 呼び出し元ゼロ、PhiBuilderBox::generate_phis() で代替
|
||||
|
||||
@ -83,7 +83,7 @@ pub struct PhiBuilderBox {
|
||||
///
|
||||
/// ## 責務分離の原則
|
||||
/// - **If側(この箱)**: ループコンテキストを「名前セット」だけで受け取る
|
||||
/// - **Loop側(LoopBuilder)**: LoopVarClassBoxから「キャリア変数名」を抽出
|
||||
/// - **Loop側(LoopBuilder)**: LoopScopeShape から「キャリア変数名」を抽出
|
||||
/// - **橋渡し**: このIfPhiContext経由で最小限の情報伝達
|
||||
///
|
||||
/// ## なぜこの設計?
|
||||
@ -102,7 +102,7 @@ pub struct IfPhiContext {
|
||||
/// - 例: `if i >= n { break }` でthen腕に`i`なし → でもcarrierなのでPHI必要
|
||||
///
|
||||
/// # 決定箇所
|
||||
/// - LoopBuilder側でLoopVarClassBoxから抽出
|
||||
/// - LoopBuilder側でLoopScopeShapeから抽出
|
||||
/// - Pinned(ループ越しパラメータ) + Carrier(ループ修正変数)
|
||||
pub loop_carrier_names: std::collections::BTreeSet<String>,
|
||||
}
|
||||
@ -498,7 +498,7 @@ impl PhiBuilderBox {
|
||||
/// │ └─ Carrier変数のPHI
|
||||
/// ├─ Exit PHI: ExitPhiBuilder使用
|
||||
/// │ ├─ BodyLocalPhiBuilder(要否判定)
|
||||
/// │ ├─ LoopVarClassBox(変数分類)
|
||||
/// │ ├─ LoopScopeShape(変数分類)
|
||||
/// │ └─ LocalScopeInspectorBox(定義追跡)
|
||||
/// └─ Seal: PhiInputCollector使用
|
||||
/// ```
|
||||
|
||||
Reference in New Issue
Block a user