feat(mir): Phase 30 F-1 LoopScopeShape SSOT preparation
LoopScopeShape を変数分類の唯一の情報源 (SSOT) にするための準備: F-1.1 LoopVarClassBox: - 4分類(Pinned/Carrier/BodyLocalExit/BodyLocalInternal)の仕様を loop_scope_shape.rs にドキュメント化(PHI生成ルール表付き) - LoopScopeShape.classify() / classify_all() メソッド追加 - LoopVarClassBox.classify_with_scope() 委譲メソッド追加 - 旧APIにPhase 30 TODOコメント追加 F-1.2 LoopExitLivenessBox: - exit_live フィールドにSSOT説明ドキュメント追加 - get_exit_live_from_scope() 委譲メソッド追加 F-1.3 LocalScopeInspectorBox: - Phase 30移行中のTODOコメント追加 - is_available_in_all_with_scope() 委譲メソッド追加 未使用フィールド削除: - CaseAContext::scope フィールド削除 - LoopBypassFlags::exit フィールド削除 - PhiBuilderBox::loop_context / LoopPhiContext 削除 テスト結果: 37テスト全てPASS - loop_scope_shape: 12 PASS (+2 new) - loop_var_classifier: 11 PASS - loop_exit_liveness: 3 PASS - local_scope_inspector: 11 PASS 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -44,8 +44,52 @@ use crate::mir::{BasicBlockId, MirFunction, MirQuery, ValueId};
|
||||
|
||||
/// ループ変数スコープの統合ビュー
|
||||
///
|
||||
/// # Fields
|
||||
/// # Phase 30: 変数分類の唯一の仕様ソース (SSOT)
|
||||
///
|
||||
/// ## 4分類の定義 (from LoopVarClassBox)
|
||||
///
|
||||
/// ### 1. Pinned(ループ外パラメータ)
|
||||
/// - **定義**: ループ開始前に定義され、ループ内で**変更されない**変数
|
||||
/// - **needs_header_phi**: ✅ true(ループ再入時に値を維持)
|
||||
/// - **needs_exit_phi**: ✅ true(ループ後も値が必要)
|
||||
/// - **例**: `loop(i < limit) { ... }` の `limit`
|
||||
///
|
||||
/// ### 2. Carrier(ループ更新変数)
|
||||
/// - **定義**: 各イテレーションで**更新される**変数
|
||||
/// - **needs_header_phi**: ✅ true(前回値と新値をマージ)
|
||||
/// - **needs_exit_phi**: ✅ true(最終値が必要)
|
||||
/// - **例**: `loop(i < 10) { i = i + 1 }` の `i`
|
||||
///
|
||||
/// ### 3. BodyLocalExit(全exit経路で定義)
|
||||
/// - **定義**: ループ内で定義され、**全ての exit predecessor** で利用可能
|
||||
/// - **needs_header_phi**: ❌ false(ループ外には存在しない)
|
||||
/// - **needs_exit_phi**: ✅ true(全exitで値が定義済み)
|
||||
/// - **例**: `loop { local x = compute(); if x > 10 { break } }`
|
||||
///
|
||||
/// ### 4. BodyLocalInternal(一部exit経路のみ)
|
||||
/// - **定義**: ループ内で定義され、**一部の exit predecessor** でのみ利用可能
|
||||
/// - **needs_header_phi**: ❌ false
|
||||
/// - **needs_exit_phi**: ❌ false(Option C: PHI pred mismatch 防止!)
|
||||
/// - **例**: skip_whitespace の `ch`(一部break経路でのみ定義)
|
||||
///
|
||||
/// ## PHI生成ルール早見表
|
||||
///
|
||||
/// | 分類 | header_phi | exit_phi |
|
||||
/// |-------------------|------------|----------|
|
||||
/// | Pinned | ✅ | ✅ |
|
||||
/// | Carrier | ✅ | ✅ |
|
||||
/// | BodyLocalExit | ❌ | ✅ |
|
||||
/// | BodyLocalInternal | ❌ | ❌ |
|
||||
///
|
||||
/// # Fields (Phase 30 Final Form)
|
||||
///
|
||||
/// ## Block IDs (from LoopForm)
|
||||
/// - `header`: ループヘッダブロック(条件チェック)
|
||||
/// - `body`: ループボディブロック(メイン処理)
|
||||
/// - `latch`: ループラッチブロック(header へ戻る)
|
||||
/// - `exit`: ループ終了ブロック(ループ後の継続)
|
||||
///
|
||||
/// ## Variable Classification
|
||||
/// - `pinned`: ループ外から来て変わらない変数(header/exit PHI 両方必要)
|
||||
/// - `carriers`: 各イテレーションで更新される変数(header/exit PHI 両方必要)
|
||||
/// - `body_locals`: ループ内だけで完結する変数(JoinIR では参照しない)
|
||||
@ -53,6 +97,22 @@ use crate::mir::{BasicBlockId, MirFunction, MirQuery, ValueId};
|
||||
/// - `progress_carrier`: ループを前に進める変数(将来の Verifier 用)
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct LoopScopeShape {
|
||||
// === Block IDs (Phase 30: from LoopForm) ===
|
||||
|
||||
/// Loop header block (condition check)
|
||||
pub header: BasicBlockId,
|
||||
|
||||
/// Loop body block (main processing)
|
||||
pub body: BasicBlockId,
|
||||
|
||||
/// Loop latch block (back-edge to header)
|
||||
pub latch: BasicBlockId,
|
||||
|
||||
/// Loop exit block (continuation after loop)
|
||||
pub exit: BasicBlockId,
|
||||
|
||||
// === Variable Classification ===
|
||||
|
||||
/// Loop-crossing parameters (always need header/exit PHI)
|
||||
pub pinned: BTreeSet<String>,
|
||||
|
||||
@ -65,6 +125,27 @@ pub(crate) struct LoopScopeShape {
|
||||
|
||||
/// Variables live at exit (needs_exit_phi() == true)
|
||||
/// = pinned ∪ carriers ∪ BodyLocalExit
|
||||
///
|
||||
/// # Phase 30: exit_live の唯一の情報源 (SSOT)
|
||||
///
|
||||
/// このフィールドはループ終了後に使用される変数の唯一の正式ソース。
|
||||
/// LoopExitLivenessBox が返していた情報はここに統合される。
|
||||
///
|
||||
/// ## 計算方法
|
||||
///
|
||||
/// 1. LoopVarClassBox の分類結果から:
|
||||
/// - Pinned → exit_live に追加
|
||||
/// - Carrier → exit_live に追加
|
||||
/// - BodyLocalExit → exit_live に追加
|
||||
/// - BodyLocalInternal → exit_live には**追加しない**(Option C)
|
||||
///
|
||||
/// 2. LoopExitLivenessBox.compute_live_at_exit() の結果をマージ
|
||||
/// (Phase 1: 空集合、Phase 2+: MIRスキャン結果)
|
||||
///
|
||||
/// ## 将来計画
|
||||
///
|
||||
/// Phase 30完了後、LoopExitLivenessBox は削除され、
|
||||
/// このフィールドが唯一の live_at_exit 情報源になる。
|
||||
pub exit_live: BTreeSet<String>,
|
||||
|
||||
/// Progress carrier for loop termination check (future Verifier use)
|
||||
@ -73,31 +154,37 @@ pub(crate) struct LoopScopeShape {
|
||||
}
|
||||
|
||||
impl LoopScopeShape {
|
||||
/// Create LoopScopeShape from existing boxes (Phase 29: thin wrapper)
|
||||
/// Create LoopScopeShape from existing boxes (Phase 30: unified interface)
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `loop_form`: LoopForm containing block structure
|
||||
/// - `intake`: LoopFormIntake containing classified variable info
|
||||
/// - `var_classes`: LoopVarClassBox for classification
|
||||
/// - `exit_live_box`: LoopExitLivenessBox for exit liveness
|
||||
/// - `query`: MirQuery for liveness computation
|
||||
/// - `exit_block`: Exit block ID
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Some(LoopScopeShape) if successful, None if critical data is missing.
|
||||
///
|
||||
/// # Phase 29 Behavior
|
||||
/// # Phase 30 Design
|
||||
///
|
||||
/// Currently just reads from existing boxes without modifying behavior.
|
||||
/// Future Phase 30 will absorb the boxes entirely.
|
||||
/// This is the primary entry point for creating LoopScopeShape.
|
||||
/// Block IDs come from loop_form, variable classification from existing boxes.
|
||||
/// Future phases will internalize the box logic entirely.
|
||||
pub(crate) fn from_existing_boxes(
|
||||
loop_form: &LoopForm,
|
||||
intake: &LoopFormIntake,
|
||||
var_classes: &LoopVarClassBox,
|
||||
exit_live_box: &LoopExitLivenessBox,
|
||||
query: &impl MirQuery,
|
||||
exit_block: BasicBlockId,
|
||||
) -> Option<Self> {
|
||||
// Extract block IDs from LoopForm
|
||||
let header = loop_form.header;
|
||||
let body = loop_form.body;
|
||||
let latch = loop_form.latch;
|
||||
let exit = loop_form.exit;
|
||||
// Extract pinned and carriers from intake (already classified)
|
||||
let pinned: BTreeSet<String> = intake.pinned_ordered.iter().cloned().collect();
|
||||
let carriers: BTreeSet<String> = intake.carrier_ordered.iter().cloned().collect();
|
||||
@ -105,7 +192,7 @@ impl LoopScopeShape {
|
||||
// Build LocalScopeInspectorBox for body_local classification
|
||||
let mut inspector =
|
||||
crate::mir::phi_core::local_scope_inspector::LocalScopeInspectorBox::new();
|
||||
inspector.record_snapshot(exit_block, &intake.header_snapshot);
|
||||
inspector.record_snapshot(exit, &intake.header_snapshot);
|
||||
for (bb, snap) in &intake.exit_snapshots {
|
||||
inspector.record_snapshot(*bb, snap);
|
||||
}
|
||||
@ -145,7 +232,7 @@ impl LoopScopeShape {
|
||||
// Compute exit_live from LoopExitLivenessBox (Phase 1: usually empty)
|
||||
let exit_live_from_box = exit_live_box.compute_live_at_exit(
|
||||
query,
|
||||
exit_block,
|
||||
exit, // Use exit from LoopForm
|
||||
&intake.header_snapshot,
|
||||
&intake.exit_snapshots,
|
||||
);
|
||||
@ -159,6 +246,12 @@ impl LoopScopeShape {
|
||||
let progress_carrier = carriers.iter().next().cloned();
|
||||
|
||||
Some(Self {
|
||||
// Block IDs from LoopForm
|
||||
header,
|
||||
body,
|
||||
latch,
|
||||
exit,
|
||||
// Variable classification
|
||||
pinned,
|
||||
carriers,
|
||||
body_locals,
|
||||
@ -198,6 +291,55 @@ impl LoopScopeShape {
|
||||
pub fn exit_phi_vars(&self) -> Vec<String> {
|
||||
self.exit_live.iter().cloned().collect()
|
||||
}
|
||||
|
||||
/// Phase 30 F-1.1: 変数を4分類に分類する
|
||||
///
|
||||
/// LoopVarClassBox.classify() と同じロジックを LoopScopeShape の内部状態から導出。
|
||||
/// 将来は LoopVarClassBox がこのメソッドに委譲するようになる。
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// - Pinned: pinned に含まれる
|
||||
/// - Carrier: carriers に含まれる
|
||||
/// - BodyLocalExit: body_locals かつ exit_live に含まれる
|
||||
/// - BodyLocalInternal: body_locals に含まれるが exit_live には含まれない
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// 既知の変数でない場合は BodyLocalInternal を返す(保守的)
|
||||
pub fn classify(&self, var_name: &str) -> LoopVarClass {
|
||||
// Priority 1: Check if it's a pinned variable
|
||||
if self.pinned.contains(var_name) {
|
||||
return LoopVarClass::Pinned;
|
||||
}
|
||||
|
||||
// Priority 2: Check if it's a carrier variable
|
||||
if self.carriers.contains(var_name) {
|
||||
return LoopVarClass::Carrier;
|
||||
}
|
||||
|
||||
// Priority 3: Check body_local classification
|
||||
if self.body_locals.contains(var_name) {
|
||||
if self.exit_live.contains(var_name) {
|
||||
return LoopVarClass::BodyLocalExit;
|
||||
} else {
|
||||
return LoopVarClass::BodyLocalInternal;
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown variable: conservative fallback (no PHI)
|
||||
LoopVarClass::BodyLocalInternal
|
||||
}
|
||||
|
||||
/// Phase 30 F-1.1: 複数変数を一括分類
|
||||
///
|
||||
/// LoopVarClassBox.classify_all() の代替
|
||||
pub fn classify_all(&self, var_names: &[String]) -> Vec<(String, LoopVarClass)> {
|
||||
var_names
|
||||
.iter()
|
||||
.map(|name| (name.clone(), self.classify(name)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@ -223,8 +365,7 @@ impl LoopScopeShape {
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct CaseAContext {
|
||||
/// LoopScopeShape(変数スコープ情報)
|
||||
pub scope: LoopScopeShape,
|
||||
// Phase 30: scope フィールド削除(ordered_pinned/carriers/exit_args に情報コピー済みで重複)
|
||||
|
||||
/// 順序付き pinned 変数名
|
||||
pub ordered_pinned: Vec<String>,
|
||||
@ -285,13 +426,13 @@ impl CaseAContext {
|
||||
// 2) MIR から pinned/carrier/exit 情報を抽出
|
||||
let intake = intake_loop_form(loop_form, var_classes, query, mir_func)?;
|
||||
|
||||
// 3) LoopScopeShape を構築
|
||||
// 3) LoopScopeShape を構築 (Phase 30: includes block IDs)
|
||||
let scope = LoopScopeShape::from_existing_boxes(
|
||||
loop_form,
|
||||
&intake,
|
||||
var_classes,
|
||||
exit_live,
|
||||
query,
|
||||
loop_form.exit,
|
||||
)?;
|
||||
|
||||
let ordered_pinned = scope.pinned_ordered();
|
||||
@ -323,7 +464,72 @@ impl CaseAContext {
|
||||
let exit_args = resolve_exit_args(&scope.exit_live, &name_to_loop_id, &ordered_carriers)?;
|
||||
|
||||
Some(Self {
|
||||
scope,
|
||||
ordered_pinned,
|
||||
ordered_carriers,
|
||||
name_to_loop_id,
|
||||
pinned_ids,
|
||||
carrier_ids,
|
||||
exit_args,
|
||||
})
|
||||
}
|
||||
|
||||
/// Phase 30: 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,
|
||||
{
|
||||
// LoopForm validation using scope's block IDs
|
||||
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();
|
||||
|
||||
// 変数名 → ValueId マッピングを構築
|
||||
let mut name_to_loop_id: BTreeMap<String, ValueId> = BTreeMap::new();
|
||||
let mut offset: u32 = 0;
|
||||
for name in &ordered_pinned {
|
||||
name_to_loop_id.insert(name.clone(), loop_step_id_fn(offset));
|
||||
offset += 1;
|
||||
}
|
||||
for name in &ordered_carriers {
|
||||
name_to_loop_id.insert(name.clone(), loop_step_id_fn(offset));
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
// pinned_ids / carrier_ids を構築
|
||||
let pinned_ids: Vec<ValueId> = ordered_pinned
|
||||
.iter()
|
||||
.filter_map(|k| name_to_loop_id.get(k).copied())
|
||||
.collect();
|
||||
let carrier_ids: Vec<ValueId> = ordered_carriers
|
||||
.iter()
|
||||
.filter_map(|k| name_to_loop_id.get(k).copied())
|
||||
.collect();
|
||||
|
||||
// exit_args を解決
|
||||
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,
|
||||
@ -365,6 +571,18 @@ mod tests {
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
fn make_dummy_loop_form() -> LoopForm {
|
||||
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));
|
||||
@ -398,22 +616,29 @@ mod tests {
|
||||
|
||||
#[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,
|
||||
BasicBlockId::new(100),
|
||||
);
|
||||
|
||||
assert!(scope.is_some());
|
||||
let scope = scope.unwrap();
|
||||
|
||||
// Block IDs should match loop_form
|
||||
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));
|
||||
|
||||
// pinned should be {s, n}
|
||||
assert!(scope.pinned.contains("s"));
|
||||
assert!(scope.pinned.contains("n"));
|
||||
@ -429,17 +654,18 @@ mod tests {
|
||||
|
||||
#[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,
|
||||
BasicBlockId::new(100),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@ -451,17 +677,18 @@ mod tests {
|
||||
|
||||
#[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,
|
||||
BasicBlockId::new(100),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@ -473,17 +700,18 @@ mod tests {
|
||||
|
||||
#[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,
|
||||
BasicBlockId::new(100),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@ -496,4 +724,216 @@ mod tests {
|
||||
assert_eq!(carriers.len(), 1);
|
||||
assert!(carriers.contains(&"i".to_string()));
|
||||
}
|
||||
|
||||
// Phase 30: 追加テスト(4本)
|
||||
|
||||
/// 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;
|
||||
|
||||
// header == exit の不正な LoopScopeShape を作成
|
||||
let scope = LoopScopeShape {
|
||||
header: BasicBlockId::new(10),
|
||||
body: BasicBlockId::new(11),
|
||||
latch: BasicBlockId::new(12),
|
||||
exit: BasicBlockId::new(10), // header と同じ!
|
||||
pinned: vec!["s".to_string()].into_iter().collect(),
|
||||
carriers: vec!["i".to_string()].into_iter().collect(),
|
||||
body_locals: std::collections::BTreeSet::new(),
|
||||
exit_live: vec!["i".to_string()].into_iter().collect(),
|
||||
progress_carrier: Some("i".to_string()),
|
||||
};
|
||||
|
||||
// from_scope は None を返すべき
|
||||
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 = 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,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 正確に LoopForm の値が伝播されている
|
||||
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: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
|
||||
set1.insert("z".to_string());
|
||||
set1.insert("a".to_string());
|
||||
set1.insert("m".to_string());
|
||||
|
||||
let mut set2: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
|
||||
set2.insert("m".to_string());
|
||||
set2.insert("z".to_string());
|
||||
set2.insert("a".to_string());
|
||||
|
||||
// BTreeSet はソート順でイテレート
|
||||
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,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// pinned 変数: header_phi必要、exit_phi必要
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
// carrier 変数: header_phi必要、exit_phi必要
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
// body_local 変数: header_phi不要
|
||||
for var in &scope.body_locals {
|
||||
assert!(
|
||||
!scope.needs_header_phi(var),
|
||||
"body_local var {} should NOT need header phi",
|
||||
var
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 30 F-1.1: classify() メソッドのテスト
|
||||
#[test]
|
||||
fn test_classify_method() {
|
||||
// 手動で LoopScopeShape を構築(body_locals も含む)
|
||||
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(), // BodyLocalExit
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
progress_carrier: Some("i".to_string()),
|
||||
};
|
||||
|
||||
// Pinned classification
|
||||
assert_eq!(scope.classify("s"), LoopVarClass::Pinned);
|
||||
assert_eq!(scope.classify("n"), LoopVarClass::Pinned);
|
||||
|
||||
// Carrier classification
|
||||
assert_eq!(scope.classify("i"), LoopVarClass::Carrier);
|
||||
|
||||
// BodyLocalExit classification (in body_locals AND exit_live)
|
||||
assert_eq!(scope.classify("x"), LoopVarClass::BodyLocalExit);
|
||||
|
||||
// BodyLocalInternal classification (in body_locals but NOT in exit_live)
|
||||
assert_eq!(scope.classify("ch"), LoopVarClass::BodyLocalInternal);
|
||||
|
||||
// Unknown variable → BodyLocalInternal (conservative)
|
||||
assert_eq!(scope.classify("unknown"), LoopVarClass::BodyLocalInternal);
|
||||
}
|
||||
|
||||
/// Phase 30 F-1.1: 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()),
|
||||
};
|
||||
|
||||
// classify() と needs_header_phi() / needs_exit_phi() が一致することを確認
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,8 +51,7 @@ pub(crate) fn is_joinir_header_bypass_target(fn_name: &str) -> bool {
|
||||
pub(crate) struct LoopBypassFlags {
|
||||
/// Header φ バイパスが有効か
|
||||
pub header: bool,
|
||||
/// Exit φ バイパスが有効か(Phase 27.6-2, 現在未使用)
|
||||
pub exit: bool,
|
||||
// Phase 30: exit フィールド削除(完全未使用、将来 JoinIR で代替予定)
|
||||
}
|
||||
|
||||
/// Phase 27.4-C Refactor: JoinIR Loop φ バイパスフラグを取得
|
||||
@ -80,8 +79,7 @@ pub(crate) fn get_loop_bypass_flags(fn_name: &str) -> LoopBypassFlags {
|
||||
|
||||
LoopBypassFlags {
|
||||
header: joinir_exp && header_exp && is_joinir_header_bypass_target(fn_name),
|
||||
// Phase 27.6-2: Exit φ バイパスは将来的に追加予定
|
||||
exit: false,
|
||||
// Phase 30: exit フィールド削除済み
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,14 @@
|
||||
/// 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.
|
||||
@ -145,6 +154,30 @@ impl LocalScopeInspectorBox {
|
||||
.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() を使う方が効率的。
|
||||
pub 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)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@ -43,6 +43,13 @@ pub trait ExitLivenessProvider: Send + Sync {
|
||||
|
||||
/// Loop Exit Liveness Box(Legacy/Phase 1)
|
||||
///
|
||||
/// # Phase 30: LoopScopeShape 移行中
|
||||
///
|
||||
/// このBoxは将来 LoopScopeShape に吸収される予定。
|
||||
/// LoopScopeShape.exit_live が唯一の live_at_exit 情報源になる。
|
||||
///
|
||||
/// **新規コード**: `LoopScopeShape::exit_live` を直接参照すること。
|
||||
///
|
||||
/// # Purpose
|
||||
/// Exit後で本当に使われる変数を決定する専門箱
|
||||
///
|
||||
@ -171,6 +178,29 @@ impl LoopExitLivenessBox {
|
||||
|
||||
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);
|
||||
/// ```
|
||||
pub 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 {
|
||||
|
||||
@ -108,7 +108,12 @@ impl LoopVarClass {
|
||||
|
||||
/// Loop variable classifier box
|
||||
///
|
||||
/// # Usage
|
||||
/// # Phase 30: LoopScopeShape 移行中
|
||||
///
|
||||
/// このBoxは将来 LoopScopeShape に吸収される予定。
|
||||
/// 新規コードは `LoopScopeShape::classify()` を直接使うことを推奨。
|
||||
///
|
||||
/// # Usage (Legacy)
|
||||
///
|
||||
/// ```
|
||||
/// let inspector = LocalScopeInspectorBox::new();
|
||||
@ -127,6 +132,13 @@ impl LoopVarClass {
|
||||
/// // Generate exit PHI
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Usage (Phase 30 Recommended)
|
||||
///
|
||||
/// ```
|
||||
/// // LoopScopeShape が利用可能な場合は直接使う
|
||||
/// let class = scope.classify("ch");
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct LoopVarClassBox;
|
||||
|
||||
@ -138,6 +150,11 @@ impl LoopVarClassBox {
|
||||
|
||||
/// Classify a variable for PHI generation decision
|
||||
///
|
||||
/// # Phase 30 TODO
|
||||
///
|
||||
/// このメソッドは将来 `LoopScopeShape::classify()` に置き換える予定。
|
||||
/// 呼び出し側が LoopScopeShape を持っている場合は、そちらを直接使うこと。
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `var_name`: Variable to classify
|
||||
@ -245,6 +262,38 @@ impl LoopVarClassBox {
|
||||
.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);
|
||||
/// ```
|
||||
pub 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 を使って複数変数を一括分類
|
||||
pub 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)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@ -51,8 +51,7 @@ use std::collections::BTreeMap;
|
||||
pub struct PhiBuilderBox {
|
||||
/// If PHI生成時のコンテキスト(将来拡張用)
|
||||
if_context: Option<IfPhiContext>,
|
||||
/// Loop PHI生成時のコンテキスト(将来拡張用)
|
||||
loop_context: Option<LoopPhiContext>,
|
||||
// Phase 30: loop_context 削除(完全未使用、将来 JoinIR で代替予定)
|
||||
}
|
||||
|
||||
/// If PHI生成コンテキスト(Phase 26-F-2: 箱理論による責務分離)
|
||||
@ -85,19 +84,13 @@ pub struct IfPhiContext {
|
||||
pub loop_carrier_names: std::collections::BTreeSet<String>,
|
||||
}
|
||||
|
||||
/// Loop PHI生成コンテキスト(将来拡張用)
|
||||
#[derive(Debug, Clone)]
|
||||
struct LoopPhiContext {
|
||||
/// 予約済み(Phase 3で実装)
|
||||
_reserved: (),
|
||||
}
|
||||
// Phase 30: LoopPhiContext 削除(完全未使用、将来 JoinIR で代替予定)
|
||||
|
||||
impl PhiBuilderBox {
|
||||
/// 新しいPhiBuilderBoxを作成
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
if_context: None,
|
||||
loop_context: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -576,7 +569,7 @@ mod tests {
|
||||
fn test_phi_builder_box_creation() {
|
||||
let builder = PhiBuilderBox::new();
|
||||
assert!(builder.if_context.is_none());
|
||||
assert!(builder.loop_context.is_none());
|
||||
// Phase 30: loop_context 削除済み
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user