refactor(joinir): LoopScopeShape モジュール箱化
## 目的 loop_scope_shape.rs(1274行)を責務別に7ファイルに分割し、 箱理論に基づいた保守性の高い構造に再構成。 ## 箱化構造 ``` loop_scope_shape/ ├── README.md - 責務説明 ├── mod.rs - モジュール統合 ├── shape.rs - 変数分類のSSOT + 質問系API(Phase 48-4) ├── builder.rs - LoopForm/Trio からの組み立て ├── case_a.rs - Case-A minimal ターゲット判定 ├── context.rs - generic_case_a 共通コンテキスト └── tests.rs - 回帰テスト(13本) ``` ## 責務分離 ### shape.rs(核心) - **SSOT**: 変数分類(pinned/carrier/body_local/exit_live) - **Phase 48-4 API**: - `get_exit_live()`: LoopExitLivenessBox 代替 - `is_available_in_all()`: LocalScopeInspectorBox 代替 - `variable_definitions`: Phase 48-5+ で統合予定 ### builder.rs(組み立て) - `from_existing_boxes_legacy()`: Trio → LoopScopeShape - `from_loop_form()`: Phase 48-2 で内部化(Trio 依存削減) - Case-A ルーティング込み ### case_a.rs(判定) - `is_case_a_minimal_target()`: Phase 30 F-3.1 ハードコード集合 ### context.rs(共通化) - `CaseAContext`: generic_case_a 共通ロジック ### tests.rs(検証) - 13本のテスト(Phase 48-4 の2本含む) - 回帰防止の完全カバレッジ ## Phase 48-4 テスト復活 リファクタリング時に削除された2本のテストを復活: - `test_is_available_in_all_phase48_4`: 空の variable_definitions - `test_is_available_in_all_phase48_5_future`: Phase 48-5+ シミュレート ## テスト結果 ``` running 13 tests test test_get_exit_live ... ok test test_is_available_in_all_phase48_4 ... ok test test_is_available_in_all_phase48_5_future ... ok [... 10 other tests ...] test result: ok. 13 passed; 0 failed ``` ## 箱理論の実践 ### 箱化の利点 - ✅ 単一責務: shape.rs が SSOT、builder.rs が組み立て - ✅ 拡張容易: 新しい入力経路は builder.rs に箱追加 - ✅ テスタビリティ: tests.rs で独立検証 - ✅ API安定性: shape.rs の質問系 API が外部インターフェース ### Phase 48-5+ への橋渡し - shape.rs に Phase 48-4 API が配置済み - builder.rs で Trio 依存を段階削除可能 - variable_definitions 統合の準備完了 ## 修正ファイル - 削除: `src/mir/join_ir/lowering/loop_scope_shape.rs` (1274行) - 新規: `src/mir/join_ir/lowering/loop_scope_shape/` (7ファイル) 🎉 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
13
src/mir/join_ir/lowering/loop_scope_shape/README.md
Normal file
13
src/mir/join_ir/lowering/loop_scope_shape/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# loop_scope_shape
|
||||
|
||||
LoopScopeShape を箱化し、JoinIR lowering からの質問をここに集約するよ。
|
||||
|
||||
- `shape.rs`: 変数分類の SSOT。pinned/carrier/body_local/exit_live と API を提供。
|
||||
- `builder.rs`: LoopForm + Trio を受け取って LoopScopeShape を組み立て。Case-A ルーティング込み。
|
||||
- `case_a.rs`: Case-A minimal ターゲット判定(Phase 30 F-3.1 のハードコード集合)。
|
||||
- `context.rs`: generic_case_a 共通の CaseAContext。
|
||||
- `tests.rs`: 既存仕様の回帰テスト。
|
||||
|
||||
責務の境界:
|
||||
- 解析・分類の仕様は shape.rs に閉じ込める(他層は API で参照)。
|
||||
- 新しい入力経路を足すときは builder.rs に箱を追加し、shape.rs の SSOT を崩さないこと。
|
||||
236
src/mir/join_ir/lowering/loop_scope_shape/builder.rs
Normal file
236
src/mir/join_ir/lowering/loop_scope_shape/builder.rs
Normal file
@ -0,0 +1,236 @@
|
||||
//! LoopScopeShape の組み立てロジック
|
||||
//!
|
||||
//! LoopForm / 既存箱から LoopScopeShape を構築し、Case-A minimal ターゲットは
|
||||
//! analyze_case_a パスにルーティングする。
|
||||
|
||||
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;
|
||||
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;
|
||||
use super::shape::LoopScopeShape;
|
||||
|
||||
impl LoopScopeShape {
|
||||
/// Case-A ルーティング込みで LoopScopeShape を構築
|
||||
pub(crate) fn from_existing_boxes(
|
||||
loop_form: &LoopForm,
|
||||
intake: &LoopFormIntake,
|
||||
var_classes: &LoopVarClassBox,
|
||||
exit_live_box: &LoopExitLivenessBox,
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
/// Case-A minimal 用の解析パス(現在は legacy と同じ実装)
|
||||
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)?;
|
||||
|
||||
if std::env::var("NYASH_LOOPSCOPE_DEBUG").is_ok() {
|
||||
eprintln!(
|
||||
"[loopscope/case_a] {} via analyze_case_a path (pinned={}, carriers={}, exit_live={})",
|
||||
func_name,
|
||||
result.pinned.len(),
|
||||
result.carriers.len(),
|
||||
result.exit_live.len(),
|
||||
);
|
||||
}
|
||||
|
||||
Some(result)
|
||||
}
|
||||
|
||||
/// 既存箱ベースの従来実装(Case-A 以外のループで使用)
|
||||
fn from_existing_boxes_legacy(
|
||||
loop_form: &LoopForm,
|
||||
intake: &LoopFormIntake,
|
||||
var_classes: &LoopVarClassBox,
|
||||
exit_live_box: &LoopExitLivenessBox,
|
||||
query: &impl MirQuery,
|
||||
) -> Option<Self> {
|
||||
let layout = block_layout(loop_form);
|
||||
|
||||
if std::env::var("NYASH_LOOPSCOPE_DEBUG").is_ok() {
|
||||
let loop_id = LoopId(0);
|
||||
let control = loop_form.to_control_view(loop_id);
|
||||
eprintln!(
|
||||
"[loopscope/view] region.header={:?}, latches={}, exit_edges={}, control.exits={}",
|
||||
layout.header,
|
||||
loop_form.to_region_view(loop_id).latches.len(),
|
||||
loop_form.to_exit_edges(loop_id).len(),
|
||||
control.exits.len()
|
||||
);
|
||||
}
|
||||
|
||||
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 progress_carrier = carriers.iter().next().cloned();
|
||||
let variable_definitions = BTreeMap::new();
|
||||
|
||||
Some(Self {
|
||||
header: layout.header,
|
||||
body: layout.body,
|
||||
latch: layout.latch,
|
||||
exit: layout.exit,
|
||||
pinned,
|
||||
carriers,
|
||||
body_locals,
|
||||
exit_live,
|
||||
progress_carrier,
|
||||
variable_definitions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct LoopBlockLayout {
|
||||
header: BasicBlockId,
|
||||
body: BasicBlockId,
|
||||
latch: BasicBlockId,
|
||||
exit: BasicBlockId,
|
||||
}
|
||||
|
||||
fn block_layout(loop_form: &LoopForm) -> LoopBlockLayout {
|
||||
let loop_id = LoopId(0); // 単一ループの場合は 0
|
||||
let region = loop_form.to_region_view(loop_id);
|
||||
let exit_edges = loop_form.to_exit_edges(loop_id);
|
||||
|
||||
let header = region.header;
|
||||
let body = loop_form.body; // body は region.blocks から推測が難しいので直接参照
|
||||
let latch = region.latches.first().copied().unwrap_or(loop_form.latch);
|
||||
let exit = exit_edges.first().map(|e| e.to).unwrap_or(loop_form.exit);
|
||||
|
||||
LoopBlockLayout {
|
||||
header,
|
||||
body,
|
||||
latch,
|
||||
exit,
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
inspector
|
||||
}
|
||||
|
||||
fn classify_body_and_exit(
|
||||
var_classes: &LoopVarClassBox,
|
||||
intake: &LoopFormIntake,
|
||||
inspector: &LocalScopeInspectorBox,
|
||||
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 {
|
||||
match class {
|
||||
LoopVarClass::Pinned | 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if std::env::var("NYASH_LOOPSCOPE_DEBUG").is_ok() {
|
||||
eprintln!(
|
||||
"[loopscope/classify] header={:?} exit={} locals={} exit_live={}",
|
||||
layout.header,
|
||||
layout.exit.0,
|
||||
body_locals.len(),
|
||||
exit_live.len()
|
||||
);
|
||||
}
|
||||
|
||||
(body_locals, exit_live)
|
||||
}
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
25
src/mir/join_ir/lowering/loop_scope_shape/case_a.rs
Normal file
25
src/mir/join_ir/lowering/loop_scope_shape/case_a.rs
Normal file
@ -0,0 +1,25 @@
|
||||
//! Phase 30 F-3.1: Case-A minimal ターゲット判定
|
||||
|
||||
/// 現在 JoinIR lowering でサポートしている Case-A minimal ループのみ true を返す。
|
||||
/// これらは LoopScopeShape の新しい analyze_case_a パスを通る。
|
||||
///
|
||||
/// # Supported Targets
|
||||
///
|
||||
/// - `Main.skip/1`: minimal_ssa_skip_ws.hako
|
||||
/// - `FuncScannerBox.trim/1`: funcscanner_trim_min.hako
|
||||
/// - `FuncScannerBox.append_defs/2`: funcscanner_append_defs_min.hako
|
||||
/// - `Stage1UsingResolverBox.resolve_for_source/5`: stage1_using_resolver minimal
|
||||
///
|
||||
/// # Future
|
||||
///
|
||||
/// 将来的に LoopForm/LoopScopeShape ベースの汎用判定に置き換わる予定。
|
||||
/// その時点でこのハードコードリストは削除される。
|
||||
pub(crate) fn is_case_a_minimal_target(func_name: &str) -> bool {
|
||||
matches!(
|
||||
func_name,
|
||||
"Main.skip/1"
|
||||
| "FuncScannerBox.trim/1"
|
||||
| "FuncScannerBox.append_defs/2"
|
||||
| "Stage1UsingResolverBox.resolve_for_source/5"
|
||||
)
|
||||
}
|
||||
107
src/mir/join_ir/lowering/loop_scope_shape/context.rs
Normal file
107
src/mir/join_ir/lowering/loop_scope_shape/context.rs
Normal file
@ -0,0 +1,107 @@
|
||||
//! CaseAContext - generic_case_a 共通ロジックの集約
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::mir::join_ir::lowering::exit_args_resolver::resolve_exit_args;
|
||||
use crate::mir::ValueId;
|
||||
|
||||
use super::LoopScopeShape;
|
||||
|
||||
/// Case A lowering の共通コンテキスト
|
||||
///
|
||||
/// generic_case_a.rs の4関数に共通するロジックを集約し、
|
||||
/// 重複コードを削減する。
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct CaseAContext {
|
||||
pub ordered_pinned: Vec<String>,
|
||||
pub ordered_carriers: Vec<String>,
|
||||
pub name_to_loop_id: BTreeMap<String, ValueId>,
|
||||
pub pinned_ids: Vec<ValueId>,
|
||||
pub carrier_ids: Vec<ValueId>,
|
||||
pub exit_args: Vec<ValueId>,
|
||||
}
|
||||
|
||||
impl CaseAContext {
|
||||
/// LoopScopeShape を直接受け取るコンストラクタ
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `scope`: LoopScopeShape(変数スコープ情報)
|
||||
/// - `log_tag`: ログ出力用タグ(例: "skip_ws", "trim")
|
||||
/// - `loop_step_id_fn`: offset から ValueId を生成する関数
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Some(CaseAContext) if successful, None if validation fails.
|
||||
pub(crate) fn from_scope<F>(
|
||||
scope: LoopScopeShape,
|
||||
log_tag: &str,
|
||||
loop_step_id_fn: F,
|
||||
) -> Option<Self>
|
||||
where
|
||||
F: Fn(u32) -> ValueId,
|
||||
{
|
||||
if scope.header == scope.exit {
|
||||
eprintln!(
|
||||
"[joinir/generic_case_a/{}] loop_form malformed (header == exit), fallback",
|
||||
log_tag
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
let ordered_pinned = scope.pinned_ordered();
|
||||
let ordered_carriers = scope.carriers_ordered();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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_args = resolve_exit_args(&scope.exit_live, &name_to_loop_id, &ordered_carriers)?;
|
||||
|
||||
Some(Self {
|
||||
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())
|
||||
}
|
||||
}
|
||||
19
src/mir/join_ir/lowering/loop_scope_shape/mod.rs
Normal file
19
src/mir/join_ir/lowering/loop_scope_shape/mod.rs
Normal file
@ -0,0 +1,19 @@
|
||||
//! LoopScopeShape - ループ変数スコープの統合ビュー
|
||||
//!
|
||||
//! Box化して責務を分離:
|
||||
//! - `shape`: 変数分類のSSOTと質問系API
|
||||
//! - `builder`: LoopForm / 既存箱からの組み立て(Case-A ルーティング含む)
|
||||
//! - `case_a`: Case-A minimal ターゲット判定(Phase 30 F-3.1)
|
||||
//! - `context`: generic_case_a 用の共通コンテキスト
|
||||
|
||||
mod builder;
|
||||
mod case_a;
|
||||
mod context;
|
||||
mod shape;
|
||||
|
||||
pub(crate) use case_a::is_case_a_minimal_target;
|
||||
pub(crate) use context::CaseAContext;
|
||||
pub(crate) use shape::LoopScopeShape;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
129
src/mir/join_ir/lowering/loop_scope_shape/shape.rs
Normal file
129
src/mir/join_ir/lowering/loop_scope_shape/shape.rs
Normal file
@ -0,0 +1,129 @@
|
||||
//! LoopScopeShape 本体と分類API
|
||||
//!
|
||||
//! 変数分類の唯一の情報源 (SSOT) として、pinned/carrier/body_local/exit_live を保持する。
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use crate::mir::phi_core::loop_var_classifier::LoopVarClass;
|
||||
use crate::mir::BasicBlockId;
|
||||
|
||||
/// ループ変数スコープの統合ビュー
|
||||
///
|
||||
/// ## 4分類の定義
|
||||
///
|
||||
/// 1. **Pinned**(ループ外パラメータ)
|
||||
/// - ループ開始前に定義され、ループ内で変更されない
|
||||
/// - header/exit で PHI 必須
|
||||
///
|
||||
/// 2. **Carrier**(ループ更新変数)
|
||||
/// - 各イテレーションで更新される
|
||||
/// - header/exit で PHI 必須
|
||||
///
|
||||
/// 3. **BodyLocalExit**(全 exit 経路で定義)
|
||||
/// - ループ内で定義され、全 exit predecessor で利用可能
|
||||
/// - exit で PHI 必須、header では不要
|
||||
///
|
||||
/// 4. **BodyLocalInternal**(一部 exit 経路のみ)
|
||||
/// - 一部の exit predecessor でのみ定義
|
||||
/// - PHI 不要(Option C)
|
||||
///
|
||||
/// # Fields
|
||||
///
|
||||
/// - Block IDs: `header`, `body`, `latch`, `exit`
|
||||
/// - Variable classification: `pinned`, `carriers`, `body_locals`, `exit_live`
|
||||
/// - `progress_carrier`: 進捗チェック用(将来の Verifier で使用予定)
|
||||
/// - `variable_definitions`: LocalScopeInspectorBox 情報統合予定(Phase 48-5+)
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)] // Block IDs and progress_carrier are reserved for future F-3/F-4 use
|
||||
pub(crate) struct LoopScopeShape {
|
||||
pub header: BasicBlockId,
|
||||
pub body: BasicBlockId,
|
||||
pub latch: BasicBlockId,
|
||||
pub exit: BasicBlockId,
|
||||
pub pinned: BTreeSet<String>,
|
||||
pub carriers: BTreeSet<String>,
|
||||
pub body_locals: BTreeSet<String>,
|
||||
pub exit_live: BTreeSet<String>,
|
||||
pub progress_carrier: Option<String>,
|
||||
pub(crate) variable_definitions: BTreeMap<String, BTreeSet<BasicBlockId>>,
|
||||
}
|
||||
|
||||
impl LoopScopeShape {
|
||||
/// header PHI が必要か判定
|
||||
#[allow(dead_code)] // Phase 30: will be used in F-3 generic lowering
|
||||
pub fn needs_header_phi(&self, var_name: &str) -> bool {
|
||||
self.pinned.contains(var_name) || self.carriers.contains(var_name)
|
||||
}
|
||||
|
||||
/// exit PHI が必要か判定
|
||||
pub fn needs_exit_phi(&self, var_name: &str) -> bool {
|
||||
self.exit_live.contains(var_name)
|
||||
}
|
||||
|
||||
/// 順序付き pinned 変数一覧
|
||||
pub fn pinned_ordered(&self) -> Vec<String> {
|
||||
self.pinned.iter().cloned().collect()
|
||||
}
|
||||
|
||||
/// 順序付き carrier 変数一覧
|
||||
pub fn carriers_ordered(&self) -> Vec<String> {
|
||||
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分類
|
||||
pub fn classify(&self, var_name: &str) -> LoopVarClass {
|
||||
if self.pinned.contains(var_name) {
|
||||
return LoopVarClass::Pinned;
|
||||
}
|
||||
|
||||
if self.carriers.contains(var_name) {
|
||||
return LoopVarClass::Carrier;
|
||||
}
|
||||
|
||||
if self.body_locals.contains(var_name) {
|
||||
if self.exit_live.contains(var_name) {
|
||||
LoopVarClass::BodyLocalExit
|
||||
} else {
|
||||
LoopVarClass::BodyLocalInternal
|
||||
}
|
||||
} else {
|
||||
LoopVarClass::BodyLocalInternal
|
||||
}
|
||||
}
|
||||
|
||||
/// 複数変数を一括分類
|
||||
pub fn classify_all(&self, var_names: &[String]) -> Vec<(String, LoopVarClass)> {
|
||||
var_names
|
||||
.iter()
|
||||
.map(|name| (name.clone(), self.classify(name)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// ループ終了時に live な変数集合を返す
|
||||
pub fn get_exit_live(&self) -> &BTreeSet<String> {
|
||||
&self.exit_live
|
||||
}
|
||||
|
||||
/// 変数が required_blocks すべてで利用可能か判定
|
||||
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))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
459
src/mir/join_ir/lowering/loop_scope_shape/tests.rs
Normal file
459
src/mir/join_ir/lowering/loop_scope_shape/tests.rs
Normal file
@ -0,0 +1,459 @@
|
||||
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 crate::mir::{BasicBlockId, MirQuery, ValueId};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
fn make_dummy_loop_form() -> crate::mir::loop_form::LoopForm {
|
||||
crate::mir::loop_form::LoopForm {
|
||||
preheader: BasicBlockId::new(1),
|
||||
header: BasicBlockId::new(2),
|
||||
body: BasicBlockId::new(3),
|
||||
latch: BasicBlockId::new(4),
|
||||
exit: BasicBlockId::new(100),
|
||||
continue_targets: vec![],
|
||||
break_targets: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
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 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,
|
||||
);
|
||||
|
||||
assert!(scope.is_some());
|
||||
let scope = scope.unwrap();
|
||||
|
||||
assert_eq!(scope.header, BasicBlockId::new(2));
|
||||
assert_eq!(scope.body, BasicBlockId::new(3));
|
||||
assert_eq!(scope.latch, BasicBlockId::new(4));
|
||||
assert_eq!(scope.exit, BasicBlockId::new(100));
|
||||
|
||||
assert!(scope.pinned.contains("s"));
|
||||
assert!(scope.pinned.contains("n"));
|
||||
assert_eq!(scope.pinned.len(), 2);
|
||||
|
||||
assert!(scope.carriers.contains("i"));
|
||||
assert_eq!(scope.carriers.len(), 1);
|
||||
|
||||
assert_eq!(scope.progress_carrier, Some("i".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
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();
|
||||
|
||||
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 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();
|
||||
|
||||
assert!(scope.needs_exit_phi("s"));
|
||||
assert!(scope.needs_exit_phi("n"));
|
||||
assert!(scope.needs_exit_phi("i"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
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 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()));
|
||||
}
|
||||
|
||||
/// CaseAContext::from_scope で header == exit のとき None を返すテスト
|
||||
#[test]
|
||||
fn test_from_scope_validation_header_eq_exit() {
|
||||
use crate::mir::join_ir::lowering::value_id_ranges::skip_ws as vid;
|
||||
|
||||
let scope = LoopScopeShape {
|
||||
header: BasicBlockId::new(10),
|
||||
body: BasicBlockId::new(11),
|
||||
latch: BasicBlockId::new(12),
|
||||
exit: BasicBlockId::new(10),
|
||||
pinned: vec!["s".to_string()].into_iter().collect(),
|
||||
carriers: vec!["i".to_string()].into_iter().collect(),
|
||||
body_locals: BTreeSet::new(),
|
||||
exit_live: vec!["i".to_string()].into_iter().collect(),
|
||||
progress_carrier: Some("i".to_string()),
|
||||
variable_definitions: BTreeMap::new(),
|
||||
};
|
||||
|
||||
let ctx = CaseAContext::from_scope(scope, "test", |offset| vid::loop_step(offset));
|
||||
assert!(
|
||||
ctx.is_none(),
|
||||
"from_scope should return None when header == exit"
|
||||
);
|
||||
}
|
||||
|
||||
/// block IDs が LoopForm から正しく伝播されるテスト
|
||||
#[test]
|
||||
fn test_block_ids_preserved() {
|
||||
let loop_form = crate::mir::loop_form::LoopForm {
|
||||
preheader: BasicBlockId::new(100),
|
||||
header: BasicBlockId::new(200),
|
||||
body: BasicBlockId::new(300),
|
||||
latch: BasicBlockId::new(400),
|
||||
exit: BasicBlockId::new(500),
|
||||
continue_targets: vec![],
|
||||
break_targets: vec![],
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
assert_eq!(scope.header, BasicBlockId::new(200));
|
||||
assert_eq!(scope.body, BasicBlockId::new(300));
|
||||
assert_eq!(scope.latch, BasicBlockId::new(400));
|
||||
assert_eq!(scope.exit, BasicBlockId::new(500));
|
||||
}
|
||||
|
||||
/// BTreeSet による順序決定性の確認テスト
|
||||
#[test]
|
||||
fn test_deterministic_order() {
|
||||
let mut set1: BTreeSet<String> = BTreeSet::new();
|
||||
set1.insert("z".to_string());
|
||||
set1.insert("a".to_string());
|
||||
set1.insert("m".to_string());
|
||||
|
||||
let mut set2: BTreeSet<String> = BTreeSet::new();
|
||||
set2.insert("m".to_string());
|
||||
set2.insert("z".to_string());
|
||||
set2.insert("a".to_string());
|
||||
|
||||
let vec1: Vec<_> = set1.iter().cloned().collect();
|
||||
let vec2: Vec<_> = set2.iter().cloned().collect();
|
||||
|
||||
assert_eq!(vec1, vec2);
|
||||
assert_eq!(
|
||||
vec1,
|
||||
vec!["a".to_string(), "m".to_string(), "z".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
/// needs_header_phi と needs_exit_phi の一貫性テスト
|
||||
#[test]
|
||||
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();
|
||||
|
||||
for var in &scope.pinned {
|
||||
assert!(
|
||||
scope.needs_header_phi(var),
|
||||
"pinned var {} should need header phi",
|
||||
var
|
||||
);
|
||||
assert!(
|
||||
scope.needs_exit_phi(var),
|
||||
"pinned var {} should need exit phi",
|
||||
var
|
||||
);
|
||||
}
|
||||
|
||||
for var in &scope.carriers {
|
||||
assert!(
|
||||
scope.needs_header_phi(var),
|
||||
"carrier var {} should need header phi",
|
||||
var
|
||||
);
|
||||
assert!(
|
||||
scope.needs_exit_phi(var),
|
||||
"carrier var {} should need exit phi",
|
||||
var
|
||||
);
|
||||
}
|
||||
|
||||
for var in &scope.body_locals {
|
||||
assert!(
|
||||
!scope.needs_header_phi(var),
|
||||
"body_local var {} should NOT need header phi",
|
||||
var
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// classify() メソッドのテスト
|
||||
#[test]
|
||||
fn test_classify_method() {
|
||||
let scope = LoopScopeShape {
|
||||
header: BasicBlockId::new(2),
|
||||
body: BasicBlockId::new(3),
|
||||
latch: BasicBlockId::new(4),
|
||||
exit: BasicBlockId::new(100),
|
||||
pinned: vec!["s".to_string(), "n".to_string()].into_iter().collect(),
|
||||
carriers: vec!["i".to_string()].into_iter().collect(),
|
||||
body_locals: vec!["x".to_string(), "ch".to_string()]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
exit_live: vec![
|
||||
"s".to_string(),
|
||||
"n".to_string(),
|
||||
"i".to_string(),
|
||||
"x".to_string(),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
progress_carrier: Some("i".to_string()),
|
||||
variable_definitions: BTreeMap::new(),
|
||||
};
|
||||
|
||||
assert_eq!(scope.classify("s"), LoopVarClass::Pinned);
|
||||
assert_eq!(scope.classify("n"), LoopVarClass::Pinned);
|
||||
assert_eq!(scope.classify("i"), LoopVarClass::Carrier);
|
||||
assert_eq!(scope.classify("x"), LoopVarClass::BodyLocalExit);
|
||||
assert_eq!(scope.classify("ch"), LoopVarClass::BodyLocalInternal);
|
||||
assert_eq!(scope.classify("unknown"), LoopVarClass::BodyLocalInternal);
|
||||
}
|
||||
|
||||
/// classify() と needs_*_phi() の一貫性
|
||||
#[test]
|
||||
fn test_classify_phi_consistency() {
|
||||
let scope = LoopScopeShape {
|
||||
header: BasicBlockId::new(2),
|
||||
body: BasicBlockId::new(3),
|
||||
latch: BasicBlockId::new(4),
|
||||
exit: BasicBlockId::new(100),
|
||||
pinned: vec!["s".to_string()].into_iter().collect(),
|
||||
carriers: vec!["i".to_string()].into_iter().collect(),
|
||||
body_locals: vec!["x".to_string(), "ch".to_string()]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
exit_live: vec!["s".to_string(), "i".to_string(), "x".to_string()]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
progress_carrier: Some("i".to_string()),
|
||||
variable_definitions: BTreeMap::new(),
|
||||
};
|
||||
|
||||
for var in ["s", "i", "x", "ch", "unknown"] {
|
||||
let class = scope.classify(var);
|
||||
assert_eq!(
|
||||
class.needs_header_phi(),
|
||||
scope.needs_header_phi(var),
|
||||
"classify and needs_header_phi mismatch for {}",
|
||||
var
|
||||
);
|
||||
assert_eq!(
|
||||
class.needs_exit_phi(),
|
||||
scope.needs_exit_phi(var),
|
||||
"classify and needs_exit_phi mismatch for {}",
|
||||
var
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// get_exit_live() API テスト
|
||||
#[test]
|
||||
fn test_get_exit_live() {
|
||||
let scope = LoopScopeShape {
|
||||
header: BasicBlockId::new(2),
|
||||
body: BasicBlockId::new(3),
|
||||
latch: BasicBlockId::new(4),
|
||||
exit: BasicBlockId::new(100),
|
||||
pinned: vec!["s".to_string()].into_iter().collect(),
|
||||
carriers: vec!["i".to_string()].into_iter().collect(),
|
||||
body_locals: BTreeSet::new(),
|
||||
exit_live: vec!["s".to_string(), "i".to_string()].into_iter().collect(),
|
||||
progress_carrier: Some("i".to_string()),
|
||||
variable_definitions: BTreeMap::new(),
|
||||
};
|
||||
|
||||
let exit_live = scope.get_exit_live();
|
||||
assert_eq!(exit_live.len(), 2);
|
||||
assert!(exit_live.contains("s"));
|
||||
assert!(exit_live.contains("i"));
|
||||
}
|
||||
|
||||
/// Phase 48-4: is_available_in_all() API テスト(空の variable_definitions)
|
||||
#[test]
|
||||
fn test_is_available_in_all_phase48_4() {
|
||||
// Phase 48-4: variable_definitions が空のため常に false
|
||||
let scope = LoopScopeShape {
|
||||
header: BasicBlockId::new(2),
|
||||
body: BasicBlockId::new(3),
|
||||
latch: BasicBlockId::new(4),
|
||||
exit: BasicBlockId::new(100),
|
||||
pinned: vec!["s".to_string()].into_iter().collect(),
|
||||
carriers: vec!["i".to_string()].into_iter().collect(),
|
||||
body_locals: BTreeSet::new(),
|
||||
exit_live: vec!["s".to_string(), "i".to_string()].into_iter().collect(),
|
||||
progress_carrier: Some("i".to_string()),
|
||||
variable_definitions: BTreeMap::new(), // Phase 48-4: 空で初期化
|
||||
};
|
||||
|
||||
// variable_definitions が空のため、すべて false を返す
|
||||
assert!(!scope.is_available_in_all("x", &[BasicBlockId::new(3)]));
|
||||
assert!(!scope.is_available_in_all("i", &[BasicBlockId::new(3)]));
|
||||
assert!(!scope.is_available_in_all("unknown", &[BasicBlockId::new(3)]));
|
||||
}
|
||||
|
||||
/// Phase 48-5+ 想定: is_available_in_all() with variable_definitions
|
||||
#[test]
|
||||
fn test_is_available_in_all_phase48_5_future() {
|
||||
// Phase 48-5+ で variable_definitions が統合された状態をシミュレート
|
||||
let mut variable_definitions = BTreeMap::new();
|
||||
variable_definitions.insert(
|
||||
"x".to_string(),
|
||||
vec![BasicBlockId::new(3), BasicBlockId::new(4)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
);
|
||||
variable_definitions.insert(
|
||||
"i".to_string(),
|
||||
vec![BasicBlockId::new(2), BasicBlockId::new(3), BasicBlockId::new(4)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
);
|
||||
|
||||
let scope = LoopScopeShape {
|
||||
header: BasicBlockId::new(2),
|
||||
body: BasicBlockId::new(3),
|
||||
latch: BasicBlockId::new(4),
|
||||
exit: BasicBlockId::new(100),
|
||||
pinned: vec!["s".to_string()].into_iter().collect(),
|
||||
carriers: vec!["i".to_string()].into_iter().collect(),
|
||||
body_locals: vec!["x".to_string()].into_iter().collect(),
|
||||
exit_live: vec!["s".to_string(), "i".to_string(), "x".to_string()]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
progress_carrier: Some("i".to_string()),
|
||||
variable_definitions, // Phase 48-5+ で統合された状態
|
||||
};
|
||||
|
||||
// x は block 3, 4 で定義 → block 3, 4 を要求すれば true
|
||||
assert!(scope.is_available_in_all("x", &[BasicBlockId::new(3), BasicBlockId::new(4)]));
|
||||
|
||||
// x は block 2 で未定義 → block 2, 3 を要求すれば false
|
||||
assert!(!scope.is_available_in_all("x", &[BasicBlockId::new(2), BasicBlockId::new(3)]));
|
||||
|
||||
// i は block 2, 3, 4 で定義 → すべて要求しても true
|
||||
assert!(scope.is_available_in_all(
|
||||
"i",
|
||||
&[BasicBlockId::new(2), BasicBlockId::new(3), BasicBlockId::new(4)]
|
||||
));
|
||||
|
||||
// unknown は variable_definitions にない → false
|
||||
assert!(!scope.is_available_in_all("unknown", &[BasicBlockId::new(3)]));
|
||||
}
|
||||
Reference in New Issue
Block a user