diff --git a/src/mir/basic_block.rs b/src/mir/basic_block.rs index d11931b9..99ed9a7b 100644 --- a/src/mir/basic_block.rs +++ b/src/mir/basic_block.rs @@ -6,7 +6,7 @@ use super::{EffectMask, MirInstruction, SpannedInstRef, SpannedInstruction, ValueId}; use crate::ast::Span; -use std::collections::HashSet; +use std::collections::BTreeSet; // Phase 69-3: HashSet → BTreeSet for determinism use std::fmt; /// Unique identifier for basic blocks within a function @@ -60,10 +60,12 @@ pub struct BasicBlock { pub terminator_span: Option, /// Predecessors in the control flow graph - pub predecessors: HashSet, + /// Phase 69-3: BTreeSet for deterministic iteration order + pub predecessors: BTreeSet, /// Successors in the control flow graph - pub successors: HashSet, + /// Phase 69-3: BTreeSet for deterministic iteration order + pub successors: BTreeSet, /// Combined effect mask for all instructions in this block pub effects: EffectMask, @@ -84,8 +86,8 @@ impl BasicBlock { instruction_spans: Vec::new(), terminator: None, terminator_span: None, - predecessors: HashSet::new(), - successors: HashSet::new(), + predecessors: BTreeSet::new(), // Phase 69-3: BTreeSet for determinism + successors: BTreeSet::new(), // Phase 69-3: BTreeSet for determinism effects: EffectMask::PURE, reachable: false, sealed: false, @@ -387,7 +389,8 @@ impl BasicBlock { } /// Check if this block dominates another block (simplified check) - pub fn dominates(&self, other: BasicBlockId, dominators: &[HashSet]) -> bool { + /// Phase 69-3: Changed to BTreeSet for determinism + pub fn dominates(&self, other: BasicBlockId, dominators: &[BTreeSet]) -> bool { if let Some(dom_set) = dominators.get(other.to_usize()) { dom_set.contains(&self.id) } else { diff --git a/src/mir/builder/control_flow.rs b/src/mir/builder/control_flow.rs index 75873efb..49ee6c43 100644 --- a/src/mir/builder/control_flow.rs +++ b/src/mir/builder/control_flow.rs @@ -542,7 +542,11 @@ impl super::MirBuilder { values.insert(*v); } } - MirInstruction::Phi { dst, inputs, type_hint: None } => { + MirInstruction::Phi { + dst, + inputs, + type_hint: None, + } => { values.insert(*dst); for (_, val) in inputs { values.insert(*val); @@ -651,7 +655,11 @@ impl super::MirBuilder { MirInstruction::Return { value } => MirInstruction::Return { value: value.map(remap_value), }, - MirInstruction::Phi { dst, inputs, type_hint: None } => MirInstruction::Phi { + MirInstruction::Phi { + dst, + inputs, + type_hint: None, + } => MirInstruction::Phi { dst: remap_value(*dst), inputs: inputs .iter() diff --git a/src/mir/builder/if_form.rs b/src/mir/builder/if_form.rs index e49f0c57..7c1e629c 100644 --- a/src/mir/builder/if_form.rs +++ b/src/mir/builder/if_form.rs @@ -22,7 +22,8 @@ impl<'a> PhiBuilderOps for ToplevelOps<'a> { inputs: Vec<(BasicBlockId, ValueId)>, ) -> Result<(), String> { // merge ブロックの先頭に PHI を挿入 - if let (Some(func), Some(_cur_bb)) = (self.0.current_function.as_mut(), self.0.current_block) + if let (Some(func), Some(_cur_bb)) = + (self.0.current_function.as_mut(), self.0.current_block) { crate::mir::ssot::cf_common::insert_phi_at_head_spanned( func, @@ -32,7 +33,11 @@ impl<'a> PhiBuilderOps for ToplevelOps<'a> { self.0.current_span, ); } else { - self.0.emit_instruction(MirInstruction::Phi { dst, inputs, type_hint: None })?; + self.0.emit_instruction(MirInstruction::Phi { + dst, + inputs, + type_hint: None, + })?; } Ok(()) } @@ -286,7 +291,10 @@ impl MirBuilder { } None => { if joinir_dryrun { - eprintln!("[Phase 61-4] ⏭️ JoinIR pattern not matched for {}, using fallback", func_name); + eprintln!( + "[Phase 61-4] ⏭️ JoinIR pattern not matched for {}, using fallback", + func_name + ); } } } diff --git a/src/mir/join_ir/lowering/loop_scope_shape.rs b/src/mir/join_ir/lowering/loop_scope_shape.rs deleted file mode 100644 index f4302adb..00000000 --- a/src/mir/join_ir/lowering/loop_scope_shape.rs +++ /dev/null @@ -1,1273 +0,0 @@ -//! LoopScopeShape - ループ変数スコープの統合ビュー -//! -//! # Purpose -//! -//! 既存の3箱(LoopVarClassBox, LoopExitLivenessBox, LocalScopeInspectorBox)の -//! 情報を1つの構造体に統合し、JoinIR lowering が参照する唯一のソースにする。 -//! -//! # Design Philosophy (Box-First) -//! -//! Phase 29/30 で以下の責務分離を実現: -//! - LoopForm: ループの「形」(preheader/header/body/latch/exit) -//! - LoopScopeShape: 変数の「役割」(pinned/carrier/body_local/exit_live) -//! - JoinIR: 関数と継続で表現された制御構造 -//! -//! # Phase 30 F-3.1 Strategy: Case-A Minimal Routing -//! -//! `from_existing_boxes` メソッドは、関数名に基づいてCase-A minimalターゲット -//! (skip_ws, trim, append_defs, stage1_using_resolver)を新しいパスに -//! ルーティングする。現在は同じ結果を返すが、将来的に `analyze_case_a` は -//! MIRベースの独立解析を行う基盤となる。 -//! -//! ## Routing Logic -//! -//! ```ignore -//! from_existing_boxes(func_name: Option<&str>) -//! ├── Some("Main.skip/1") → analyze_case_a() -//! ├── Some("FuncScannerBox.trim/1") → analyze_case_a() -//! ├── Some("FuncScannerBox.append_defs/2") → analyze_case_a() -//! ├── Some("Stage1UsingResolverBox.resolve_for_source/5") → analyze_case_a() -//! └── None or other → from_existing_boxes_legacy() -//! ``` -//! -//! # Usage -//! -//! ```ignore -//! let scope = LoopScopeShape::from_existing_boxes( -//! &loop_form, -//! &intake, -//! &var_classes, -//! &exit_live, -//! query, -//! Some("Main.skip/1"), // Case-A minimal target -//! )?; -//! -//! // JoinIR lowering では scope のフィールドを参照 -//! for name in &scope.pinned { -//! // ... -//! } -//! ``` - -use std::collections::{BTreeMap, BTreeSet}; - -use crate::mir::control_form::LoopId; -use crate::mir::join_ir::lowering::exit_args_resolver::resolve_exit_args; -use crate::mir::join_ir::lowering::loop_form_intake::LoopFormIntake; -use crate::mir::loop_form::LoopForm; -use crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox; -use crate::mir::phi_core::loop_var_classifier::{LoopVarClass, LoopVarClassBox}; -use crate::mir::{BasicBlockId, MirQuery, ValueId}; - -// ============================================================================ -// Phase 30 F-3.1: Case-A Minimal Target Detection -// ============================================================================ - -/// 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" - ) -} - -/// ループ変数スコープの統合ビュー -/// -/// # 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 では参照しない) -/// - `exit_live`: ループ後に使われる変数(needs_exit_phi() == true) -/// - `progress_carrier`: ループを前に進める変数(将来の Verifier 用) -/// -/// # Phase 30 Note -/// -/// Block ID フィールド (header/body/latch/exit) と progress_carrier は -/// F-3/F-4 で使用予定。現在は SSOT として保持。 -#[derive(Debug, Clone)] -#[allow(dead_code)] // Phase 30: block IDs and progress_carrier are for future F-3/F-4 use -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, - - /// Loop-modified variables (always need header/exit PHI) - pub carriers: BTreeSet, - - /// Body-local variables (JoinIR doesn't need these) - /// Includes both BodyLocalExit and BodyLocalInternal - pub body_locals: BTreeSet, - - /// Variables live at exit (needs_exit_phi() == true) - /// = pinned ∪ carriers ∪ BodyLocalExit - /// - /// # 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, - - /// Progress carrier for loop termination check (future Verifier use) - /// Typically the loop index variable (i, pos, etc.) - pub progress_carrier: Option, - - /// Phase 48-4: 変数定義ブロックのマッピング - /// - /// LocalScopeInspectorBox の var_definitions 情報を統合。 - /// - /// # Phase 48-4 Note - /// - /// 現在は空の BTreeMap で初期化(API のみ提供)。 - /// Phase 48-5+ で from_existing_boxes_legacy から LocalScopeInspectorBox の情報を抽出して統合予定。 - /// - /// # Structure - /// - /// - Key: 変数名 - /// - Value: その変数が定義されているブロック ID の集合 - /// - /// # Usage - /// - /// `is_available_in_all()` メソッドで、変数が全指定ブロックで利用可能かを判定。 - pub(crate) variable_definitions: BTreeMap>, -} - -impl LoopScopeShape { - /// Create LoopScopeShape from existing boxes (Phase 30 F-3.1: 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 - /// - `func_name`: Optional function name for Case-A minimal routing - /// - /// # Returns - /// - /// Some(LoopScopeShape) if successful, None if critical data is missing. - /// - /// # Phase 30 F-3.1 Design - /// - /// This is the primary entry point for creating LoopScopeShape. - /// - Case-A minimal targets route through `analyze_case_a` (new path) - /// - Other loops use `from_existing_boxes_legacy` (existing path) - /// - /// Both paths currently produce the same result, but analyze_case_a - /// is the foundation for future MIR-based independent analysis. - 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 { - // Phase 30 F-3.1: Route Case-A minimal targets through new path - 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, - ); - } - } - - // Default: use legacy path - Self::from_existing_boxes_legacy(loop_form, intake, var_classes, exit_live_box, query) - } - - /// Phase 48-2: Trio引数なしでLoopScopeShapeを構築 - /// - /// # Purpose - /// - /// 外部からTrioを渡す必要をなくし、LoopScopeShape内部に完全吸収。 - /// `loop_to_join.rs` などで空のTrioを作成して渡すパターンを解消する。 - /// - /// # Arguments - /// - /// - `loop_form`: LoopForm containing block structure - /// - `intake`: LoopFormIntake containing classified variable info - /// - `query`: MirQuery for liveness computation - /// - `func_name`: Optional function name for Case-A minimal routing - /// - /// # Returns - /// - /// Some(LoopScopeShape) if successful, None if critical data is missing. - /// - /// # History - /// - /// - Phase 48-2: 新規追加(Trio依存の内部化) - pub(crate) fn from_loop_form( - loop_form: &LoopForm, - intake: &LoopFormIntake, - query: &impl MirQuery, - func_name: Option<&str>, - ) -> Option { - // Phase 48-2: Trio を内部で作成(空箱) - let var_classes = LoopVarClassBox::new(); - let exit_live_box = LoopExitLivenessBox::new(); - - // 既存の from_existing_boxes を呼び出し - Self::from_existing_boxes( - loop_form, - intake, - &var_classes, - &exit_live_box, - query, - func_name, - ) - } - - /// Check if a variable needs 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) - } - - /// Check if a variable needs exit PHI - pub fn needs_exit_phi(&self, var_name: &str) -> bool { - self.exit_live.contains(var_name) - } - - /// Get ordered pinned variables (for JoinIR parameter generation) - pub fn pinned_ordered(&self) -> Vec { - self.pinned.iter().cloned().collect() - } - - /// Get ordered carrier variables (for JoinIR parameter generation) - pub fn carriers_ordered(&self) -> Vec { - self.carriers.iter().cloned().collect() - } - - /// Get all variables that need header PHI (pinned + carriers) - #[allow(dead_code)] // Phase 30: will be used in F-3 generic lowering - pub fn header_phi_vars(&self) -> Vec { - let mut result: Vec = self.pinned.iter().cloned().collect(); - result.extend(self.carriers.iter().cloned()); - result - } - - /// Get all variables that need exit PHI - #[allow(dead_code)] // Phase 30: will be used in F-3 generic lowering - pub fn exit_phi_vars(&self) -> Vec { - self.exit_live.iter().cloned().collect() - } - - // ========================================================================= - // Phase 30 F-3.1: Case-A Minimal Analysis Path - // ========================================================================= - - /// Phase 30 F-3.1: Case-A minimal 用の解析パス - /// - /// 現在は `from_existing_boxes_legacy` と同じ実装だが、 - /// debug_assert で結果を検証し、将来的に MIR ベースの独立実装に移行する。 - /// - /// # 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 - /// - `func_name`: 関数名(ログ用) - /// - /// # Returns - /// - /// Some(LoopScopeShape) if successful, None if critical data is missing. - fn analyze_case_a( - loop_form: &LoopForm, - intake: &LoopFormIntake, - var_classes: &LoopVarClassBox, - exit_live_box: &LoopExitLivenessBox, - query: &impl MirQuery, - func_name: &str, - ) -> Option { - // Phase 30 F-3.1: 現在は legacy と同じ実装 - // 将来は MIR から独立して pinned/carriers/body_locals/exit_live を計算 - let result = - Self::from_existing_boxes_legacy(loop_form, intake, var_classes, exit_live_box, query)?; - - // Debug: Case-A minimal path が使われていることをログ - 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(), - ); - } - - // TODO (F-3.1+): MIR ベースの独立計算を実装し、ここで debug_assert_eq! する - // let mir_based_result = Self::compute_from_mir(...); - // debug_assert_eq!(result.pinned, mir_based_result.pinned); - // debug_assert_eq!(result.carriers, mir_based_result.carriers); - - Some(result) - } - - /// Phase 30 F-3.1: 従来の既存箱ベース実装(legacy path) - /// - /// analyze_case_a と分離することで、Case-A minimal だけ新パスを通せる。 - /// - /// # Phase 32 Step 3-B: View 経由でブロック情報を取得 - /// - /// 直接 `loop_form.header` などを読む代わりに、`to_region_view()` 経由で取得。 - /// 現在は同じ結果だが、将来 LoopRegion が独自情報を持つようになった時に差し替え可能。 - fn from_existing_boxes_legacy( - loop_form: &LoopForm, - intake: &LoopFormIntake, - var_classes: &LoopVarClassBox, - exit_live_box: &LoopExitLivenessBox, - query: &impl MirQuery, - ) -> Option { - // Phase 32 Step 3-B: Extract block IDs via view (情報源を view に切り替え) - 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); - - // View からブロック 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); - - // Debug: view 経由の情報をログ - if std::env::var("NYASH_LOOPSCOPE_DEBUG").is_ok() { - let control = loop_form.to_control_view(loop_id); - eprintln!( - "[loopscope/view] region.header={:?}, latches={}, exit_edges={}, control.exits={}", - region.header, - region.latches.len(), - exit_edges.len(), - control.exits.len() - ); - } - - // Extract pinned and carriers from intake (already classified) - let pinned: BTreeSet = intake.pinned_ordered.iter().cloned().collect(); - let carriers: BTreeSet = intake.carrier_ordered.iter().cloned().collect(); - - // Build LocalScopeInspectorBox for body_local classification - let mut inspector = - crate::mir::phi_core::local_scope_inspector::LocalScopeInspectorBox::new(); - inspector.record_snapshot(exit, &intake.header_snapshot); - for (bb, snap) in &intake.exit_snapshots { - inspector.record_snapshot(*bb, snap); - } - - // Classify all variables to find body_locals - let all_names: Vec = intake.header_snapshot.keys().cloned().collect(); - let classified = var_classes.classify_all( - &all_names, - &intake.pinned_ordered, - &intake.carrier_ordered, - &inspector, - &intake.exit_preds, - ); - - let mut body_locals: BTreeSet = BTreeSet::new(); - let mut exit_live: BTreeSet = BTreeSet::new(); - - for (name, class) in &classified { - match class { - LoopVarClass::Pinned => { - exit_live.insert(name.clone()); - } - LoopVarClass::Carrier => { - exit_live.insert(name.clone()); - } - LoopVarClass::BodyLocalExit => { - body_locals.insert(name.clone()); - exit_live.insert(name.clone()); - } - LoopVarClass::BodyLocalInternal => { - body_locals.insert(name.clone()); - // NOT in exit_live (Option C: skip exit PHI) - } - } - } - - // Compute exit_live from LoopExitLivenessBox (Phase 1: usually empty) - let exit_live_from_box = exit_live_box.compute_live_at_exit( - query, - exit, // Use exit from LoopForm - &intake.header_snapshot, - &intake.exit_snapshots, - ); - - // Merge: exit_live = (classification-based) ∪ (liveness-box-based) - for name in exit_live_from_box { - exit_live.insert(name); - } - - // Determine progress_carrier (heuristic: first carrier, typically 'i') - let progress_carrier = carriers.iter().next().cloned(); - - // Phase 48-4: variable_definitions を空で初期化(API のみ提供) - // Phase 48-5+ で LocalScopeInspectorBox から情報を抽出して統合予定 - let variable_definitions = BTreeMap::new(); - - Some(Self { - // Block IDs from LoopForm - header, - body, - latch, - exit, - // Variable classification - pinned, - carriers, - body_locals, - exit_live, - progress_carrier, - // Phase 48-4: Trio 質問 API サポート - variable_definitions, - }) - } - - /// 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() - } - - // ======================================================================== - // Phase 48-4: Trio 質問 API の統合 - // ======================================================================== - - /// Phase 48-4: ループ終了時に live な変数集合を返す - /// - /// LoopExitLivenessBox::compute_live_at_exit() の代替 API。 - /// - /// # Returns - /// - /// ループ終了後に使用される変数の集合(pinned + carriers + BodyLocalExit) - /// - /// # Phase 48-4 設計 - /// - /// この API は「質問だけの薄い箱」原則に基づく: - /// - callsite は `scope.get_exit_live()` という質問形式でアクセス - /// - フィールド直接参照より API 安定性が高い - /// - 将来 exit_live の実装変更に強い - /// - /// # Example - /// - /// ```ignore - /// let exit_live = scope.get_exit_live(); - /// for var in exit_live { - /// // exit PHI 生成 - /// } - /// ``` - pub fn get_exit_live(&self) -> &BTreeSet { - &self.exit_live - } - - /// Phase 48-4: 変数が全指定ブロックで利用可能か判定 - /// - /// LocalScopeInspectorBox::is_available_in_all() の代替 API。 - /// - /// # Arguments - /// - /// - `var_name`: 判定する変数名 - /// - `required_blocks`: 全てで利用可能であるべきブロック集合 - /// - /// # Returns - /// - /// - `true`: 変数が全指定ブロックで利用可能(全ブロックで定義済み) - /// - `false`: 一部ブロックで未定義、または変数が存在しない - /// - /// # Phase 48-4 実装状況 - /// - /// 現在は `variable_definitions` が空のため常に false を返す(API のみ提供)。 - /// Phase 48-5+ で from_existing_boxes_legacy から LocalScopeInspectorBox の情報を抽出して統合予定。 - /// - /// # Phase 48-5+ 実装計画 - /// - /// ```ignore - /// // LocalScopeInspectorBox から情報抽出 - /// for var in all_vars { - /// let def_blocks = inspector.get_defining_blocks(var); - /// variable_definitions.insert(var.clone(), def_blocks); - /// } - /// ``` - /// - /// # Example - /// - /// ```ignore - /// // BodyLocalExit vs BodyLocalInternal の判定に使用 - /// if scope.is_available_in_all(&var_name, &[body, exit]) { - /// LoopVarClass::BodyLocalExit - /// } else { - /// LoopVarClass::BodyLocalInternal - /// } - /// ``` - 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 - } - } -} - -// ============================================================================ -// CaseAContext - generic_case_a 共通ロジックの集約 -// ============================================================================ - -/// Case A lowering の共通コンテキスト -/// -/// generic_case_a.rs の4関数に共通するロジックを集約し、 -/// 約200行の重複コードを削減する。 -/// -/// # Usage -/// -/// ```ignore -/// let scope = LoopScopeShape::from_existing_boxes(...)?; -/// let ctx = CaseAContext::from_scope(scope, "skip_ws", |offset| vid::loop_step(offset))?; -/// -/// // ctx から必要な情報を取り出して JoinModule を構築 -/// let header_shape = LoopHeaderShape::new_manual(ctx.pinned_ids.clone(), ctx.carrier_ids.clone()); -/// ``` -#[derive(Debug, Clone)] -pub(crate) struct CaseAContext { - // Phase 30: scope フィールド削除(ordered_pinned/carriers/exit_args に情報コピー済みで重複) - /// 順序付き pinned 変数名 - pub ordered_pinned: Vec, - - /// 順序付き carrier 変数名 - pub ordered_carriers: Vec, - - /// 変数名 → ループ関数内 ValueId のマッピング - pub name_to_loop_id: BTreeMap, - - /// pinned 変数の ValueId 列 - pub pinned_ids: Vec, - - /// carrier 変数の ValueId 列 - pub carrier_ids: Vec, - - /// exit 時に渡す引数の ValueId 列 - pub exit_args: Vec, -} - -impl CaseAContext { - /// LoopScopeShape を直接受け取るコンストラクタ (Phase 30) - /// - /// # 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( - scope: LoopScopeShape, - log_tag: &str, - loop_step_id_fn: F, - ) -> Option - 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 = 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 = ordered_pinned - .iter() - .filter_map(|k| name_to_loop_id.get(k).copied()) - .collect(); - let carrier_ids: Vec = ordered_carriers - .iter() - .filter_map(|k| name_to_loop_id.get(k).copied()) - .collect(); - - // exit_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, - pinned_ids, - carrier_ids, - exit_args, - }) - } - - /// 変数名から loop 関数内の ValueId を取得 - pub fn get_loop_id(&self, name: &str) -> Option { - self.name_to_loop_id.get(name).copied() - } - - /// pinned 変数の n 番目の名前を取得(なければ 0 番目を使う) - pub fn pinned_name_or_first(&self, index: usize) -> Option { - self.ordered_pinned - .get(index) - .cloned() - .or_else(|| self.ordered_pinned.first().cloned()) - } - - /// carrier 変数の n 番目の名前を取得(なければ 0 番目を使う) - pub fn carrier_name_or_first(&self, index: usize) -> Option { - self.ordered_carriers - .get(index) - .cloned() - .or_else(|| self.ordered_carriers.first().cloned()) - } -} - -// ============================================================================ -// Unit Tests -// ============================================================================ - -#[cfg(test)] -mod tests { - use super::*; - use crate::mir::ValueId; - use std::collections::BTreeMap; - - fn make_dummy_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)); - header_snapshot.insert("n".to_string(), ValueId(20)); - header_snapshot.insert("i".to_string(), ValueId(30)); - - LoopFormIntake { - pinned_ordered: vec!["s".to_string(), "n".to_string()], - carrier_ordered: vec!["i".to_string()], - header_snapshot, - exit_snapshots: vec![], - exit_preds: vec![], - } - } - - struct EmptyQuery; - impl MirQuery for EmptyQuery { - fn insts_in_block(&self, _bb: BasicBlockId) -> &[crate::mir::MirInstruction] { - &[] - } - fn succs(&self, _bb: BasicBlockId) -> Vec { - Vec::new() - } - fn reads_of(&self, _inst: &crate::mir::MirInstruction) -> Vec { - Vec::new() - } - fn writes_of(&self, _inst: &crate::mir::MirInstruction) -> Vec { - Vec::new() - } - } - - #[test] - fn test_from_existing_boxes_basic() { - let 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, // generic test - use legacy path - ); - - 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")); - assert_eq!(scope.pinned.len(), 2); - - // carriers should be {i} - assert!(scope.carriers.contains("i")); - assert_eq!(scope.carriers.len(), 1); - - // progress_carrier should be "i" - assert_eq!(scope.progress_carrier, Some("i".to_string())); - } - - #[test] - fn test_needs_header_phi() { - let 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, // generic test - use legacy path - ) - .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, // generic test - use legacy path - ) - .unwrap(); - - // Pinned and Carrier should need exit PHI - assert!(scope.needs_exit_phi("s")); - assert!(scope.needs_exit_phi("n")); - assert!(scope.needs_exit_phi("i")); - } - - #[test] - fn test_ordered_accessors() { - let 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, // generic test - use legacy path - ) - .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())); - } - - // 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()), - variable_definitions: BTreeMap::new(), // Phase 48-4 - }; - - // 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, - None, // generic test - use legacy path - ) - .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 = 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 = 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, - None, // generic test - use legacy path - ) - .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()), - variable_definitions: BTreeMap::new(), // Phase 48-4 - }; - - // 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()), - variable_definitions: BTreeMap::new(), // Phase 48-4 - }; - - // 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 - ); - } - } - - // ======================================================================== - // Phase 48-4: Trio 質問 API のテスト - // ======================================================================== - - /// Phase 48-4: 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)])); - } -} diff --git a/src/mir/join_ir/lowering/loop_scope_shape/builder.rs b/src/mir/join_ir/lowering/loop_scope_shape/builder.rs index 0daf5fc5..c1383e9f 100644 --- a/src/mir/join_ir/lowering/loop_scope_shape/builder.rs +++ b/src/mir/join_ir/lowering/loop_scope_shape/builder.rs @@ -2,18 +2,36 @@ //! //! 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 完了)。 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; +use super::case_a::{is_case_a_minimal_target, validate_case_a_structural}; use super::shape::LoopScopeShape; impl LoopScopeShape { @@ -62,7 +80,7 @@ impl LoopScopeShape { ) } - /// Case-A minimal 用の解析パス(現在は legacy と同じ実装) + /// Case-A minimal 用の解析パス(Phase 48-5: 構造判定検証追加) fn analyze_case_a( loop_form: &LoopForm, intake: &LoopFormIntake, @@ -74,6 +92,9 @@ impl LoopScopeShape { let result = Self::from_existing_boxes_legacy(loop_form, intake, var_classes, exit_live_box, query)?; + // Phase 48-5: 構造判定検証(警告のみ、将来的に厳格化) + validate_case_a_structural(loop_form, &result, func_name); + if std::env::var("NYASH_LOOPSCOPE_DEBUG").is_ok() { eprintln!( "[loopscope/case_a] {} via analyze_case_a path (pinned={}, carriers={}, exit_live={})", @@ -88,6 +109,18 @@ impl LoopScopeShape { } /// 既存箱ベースの従来実装(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( loop_form: &LoopForm, intake: &LoopFormIntake, @@ -123,7 +156,7 @@ impl LoopScopeShape { } let progress_carrier = carriers.iter().next().cloned(); - let variable_definitions = BTreeMap::new(); + let variable_definitions = extract_variable_definitions(&inspector); Some(Self { header: layout.header, @@ -166,6 +199,10 @@ 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); @@ -175,6 +212,11 @@ fn build_inspector(intake: &LoopFormIntake, exit: BasicBlockId) -> LocalScopeIns inspector } +/// 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, @@ -221,6 +263,11 @@ 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, @@ -234,3 +281,22 @@ fn merge_exit_live_from_box( &intake.exit_snapshots, ) } + +/// Phase 48-4: LocalScopeInspectorBox から variable_definitions を抽出 +/// +/// inspector.all_variables() と get_defining_blocks() を使って、 +/// 変数名 → 定義ブロック集合のマッピングを構築する。 +/// この関数は from_existing_boxes_legacy からのみ呼ばれ、Trio を直接扱う。 +fn extract_variable_definitions( + inspector: &LocalScopeInspectorBox, +) -> BTreeMap> { + let mut var_defs = BTreeMap::new(); + for var_name in inspector.all_variables() { + let blocks: BTreeSet = inspector + .get_defining_blocks(&var_name) + .into_iter() + .collect(); + var_defs.insert(var_name, blocks); + } + var_defs +} diff --git a/src/mir/join_ir/lowering/loop_scope_shape/case_a.rs b/src/mir/join_ir/lowering/loop_scope_shape/case_a.rs index ad7b4383..532b9d45 100644 --- a/src/mir/join_ir/lowering/loop_scope_shape/case_a.rs +++ b/src/mir/join_ir/lowering/loop_scope_shape/case_a.rs @@ -1,4 +1,12 @@ //! Phase 30 F-3.1: Case-A minimal ターゲット判定 +//! +//! Phase 48-5 で構造ベース判定を追加。 +//! Phase 48-5.5 で LoopStructuralAnalysis 箱化モジュール化。 + +use crate::mir::loop_form::LoopForm; + +use super::shape::LoopScopeShape; +use super::structural::LoopStructuralAnalysis; /// 現在 JoinIR lowering でサポートしている Case-A minimal ループのみ true を返す。 /// これらは LoopScopeShape の新しい analyze_case_a パスを通る。 @@ -10,10 +18,10 @@ /// - `FuncScannerBox.append_defs/2`: funcscanner_append_defs_min.hako /// - `Stage1UsingResolverBox.resolve_for_source/5`: stage1_using_resolver minimal /// -/// # Future +/// # Phase 48-5: 構造ベース判定への移行 /// -/// 将来的に LoopForm/LoopScopeShape ベースの汎用判定に置き換わる予定。 -/// その時点でこのハードコードリストは削除される。 +/// 名前ハードコードは後方互換性のために保持。 +/// 構造判定は `validate_case_a_structural()` で検証される。 pub(crate) fn is_case_a_minimal_target(func_name: &str) -> bool { matches!( func_name, @@ -23,3 +31,35 @@ pub(crate) fn is_case_a_minimal_target(func_name: &str) -> bool { | "Stage1UsingResolverBox.resolve_for_source/5" ) } + +/// Phase 48-5.5: analyze_case_a 内で構造判定を検証 +/// +/// 名前ハードコードで Case-A と判定されたループが、構造的にも Case-A の性質を持つか検証する。 +/// 将来的に名前ハードコードを削除し、構造判定のみに移行するための準備。 +/// +/// # Phase 48-5.5: 箱化モジュール化 +/// +/// LoopStructuralAnalysis 箱を使用して構造判定を実行。 +/// is_case_a_structural() は LoopStructuralAnalysis::is_case_a_minimal() に統合された。 +/// +/// # Returns +/// +/// - `true`: 構造判定も通過(正常) +/// - `false`: 構造判定に失敗(警告ログ) +pub(crate) fn validate_case_a_structural( + loop_form: &LoopForm, + scope: &LoopScopeShape, + func_name: &str, +) -> bool { + let analysis = LoopStructuralAnalysis::from_loop_scope(loop_form, scope); + let is_structural = analysis.is_case_a_minimal(); + + if !is_structural { + eprintln!( + "[case_a/warning] {} is marked as Case-A by name, but fails structural check", + func_name + ); + } + + is_structural +} diff --git a/src/mir/join_ir/lowering/loop_scope_shape/mod.rs b/src/mir/join_ir/lowering/loop_scope_shape/mod.rs index 31f5870a..c7d1eb47 100644 --- a/src/mir/join_ir/lowering/loop_scope_shape/mod.rs +++ b/src/mir/join_ir/lowering/loop_scope_shape/mod.rs @@ -5,15 +5,18 @@ //! - `builder`: LoopForm / 既存箱からの組み立て(Case-A ルーティング含む) //! - `case_a`: Case-A minimal ターゲット判定(Phase 30 F-3.1) //! - `context`: generic_case_a 用の共通コンテキスト +//! - `structural`: ループの構造的性質解析(Phase 48-5.5) mod builder; mod case_a; mod context; mod shape; +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; diff --git a/src/mir/join_ir/lowering/loop_scope_shape/tests.rs b/src/mir/join_ir/lowering/loop_scope_shape/tests.rs index 0615f80c..0b777402 100644 --- a/src/mir/join_ir/lowering/loop_scope_shape/tests.rs +++ b/src/mir/join_ir/lowering/loop_scope_shape/tests.rs @@ -457,3 +457,106 @@ fn test_is_available_in_all_phase48_5_future() { // unknown は variable_definitions にない → false assert!(!scope.is_available_in_all("unknown", &[BasicBlockId::new(3)])); } + +/// Phase 48-4: from_existing_boxes で variable_definitions が埋まることを確認 +#[test] +fn test_variable_definitions_from_inspector() { + let loop_form = make_dummy_loop_form(); + let mut intake = make_dummy_intake(); + + // exit_snapshots を追加(複数の exit predecessor をシミュレート) + let mut exit1_snap = BTreeMap::new(); + exit1_snap.insert("s".to_string(), ValueId(11)); + exit1_snap.insert("n".to_string(), ValueId(21)); + exit1_snap.insert("i".to_string(), ValueId(31)); + exit1_snap.insert("x".to_string(), ValueId(41)); // x は exit1 でのみ利用可能 + + let mut exit2_snap = BTreeMap::new(); + exit2_snap.insert("s".to_string(), ValueId(12)); + exit2_snap.insert("n".to_string(), ValueId(22)); + exit2_snap.insert("i".to_string(), ValueId(32)); + + intake.exit_snapshots = vec![ + (BasicBlockId::new(10), exit1_snap), + (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(); + + // s, n, i は両方の exit で利用可能 → is_available_in_all should be true + assert!(scope.is_available_in_all("s", &[BasicBlockId::new(10), BasicBlockId::new(11)])); + assert!(scope.is_available_in_all("n", &[BasicBlockId::new(10), BasicBlockId::new(11)])); + assert!(scope.is_available_in_all("i", &[BasicBlockId::new(10), BasicBlockId::new(11)])); + + // x は exit1 (block 10) でのみ利用可能 → both blocks requires should be false + assert!(!scope.is_available_in_all("x", &[BasicBlockId::new(10), BasicBlockId::new(11)])); + + // x は exit1 (block 10) のみなら true + assert!(scope.is_available_in_all("x", &[BasicBlockId::new(10)])); +} + +/// Phase 48-4: 一部のブロックでのみ利用可能な変数の判定 +#[test] +fn test_variable_definitions_partial_availability() { + let loop_form = make_dummy_loop_form(); + let mut intake = make_dummy_intake(); + + // 3つの exit predecessor を用意 + let mut exit1_snap = BTreeMap::new(); + exit1_snap.insert("s".to_string(), ValueId(10)); + exit1_snap.insert("y".to_string(), ValueId(50)); // y は exit1 でのみ + + let mut exit2_snap = BTreeMap::new(); + exit2_snap.insert("s".to_string(), ValueId(11)); + exit2_snap.insert("z".to_string(), ValueId(60)); // z は exit2 でのみ + + let mut exit3_snap = BTreeMap::new(); + exit3_snap.insert("s".to_string(), ValueId(12)); + // s のみ + + intake.exit_snapshots = vec![ + (BasicBlockId::new(20), exit1_snap), + (BasicBlockId::new(21), exit2_snap), + (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(); + + // s は全 exit で利用可能 + assert!(scope.is_available_in_all( + "s", + &[BasicBlockId::new(20), BasicBlockId::new(21), BasicBlockId::new(22)] + )); + + // y は exit1 でのみ利用可能 + assert!(scope.is_available_in_all("y", &[BasicBlockId::new(20)])); + assert!(!scope.is_available_in_all("y", &[BasicBlockId::new(20), BasicBlockId::new(21)])); + + // z は exit2 でのみ利用可能 + assert!(scope.is_available_in_all("z", &[BasicBlockId::new(21)])); + assert!(!scope.is_available_in_all("z", &[BasicBlockId::new(21), BasicBlockId::new(22)])); +} diff --git a/src/mir/loop_builder/if_in_loop_phi_emitter.rs b/src/mir/loop_builder/if_in_loop_phi_emitter.rs index 31910ab7..c36d6cdf 100644 --- a/src/mir/loop_builder/if_in_loop_phi_emitter.rs +++ b/src/mir/loop_builder/if_in_loop_phi_emitter.rs @@ -68,12 +68,13 @@ impl IfInLoopPhiEmitter { if_shape: &IfShape, ) -> Result { let mut phi_count = 0; - let trace_on = std::env::var("HAKO_JOINIR_IF_IN_LOOP_TRACE").ok().as_deref() == Some("1"); + let trace_on = std::env::var("HAKO_JOINIR_IF_IN_LOOP_TRACE") + .ok() + .as_deref() + == Some("1"); if trace_on { - eprintln!( - "[Phase 61-3] IfInLoopPhiEmitter::emit_header_phis start" - ); + eprintln!("[Phase 61-3] IfInLoopPhiEmitter::emit_header_phis start"); eprintln!("[Phase 61-3] header_phis: {:?}", phi_spec.header_phis); eprintln!("[Phase 61-3] carrier_names: {:?}", carrier_names); } @@ -99,10 +100,7 @@ impl IfInLoopPhiEmitter { }; // Then値: snapshot から取得、なければ pre_val - let then_val = then_snapshot - .get(var_name) - .copied() - .unwrap_or(pre_val); + let then_val = then_snapshot.get(var_name).copied().unwrap_or(pre_val); // Else値: snapshot から取得、なければ pre_val(片腕 PHI パターン) let else_val = else_snapshot_opt @@ -181,8 +179,10 @@ impl IfInLoopPhiEmitter { if_shape: &IfShape, ) -> Result { let mut phi_count = 0; - let trace_on = - std::env::var("HAKO_JOINIR_IF_TOPLEVEL_TRACE").ok().as_deref() == Some("1"); + let trace_on = std::env::var("HAKO_JOINIR_IF_TOPLEVEL_TRACE") + .ok() + .as_deref() + == Some("1"); if trace_on { eprintln!("[Phase 61-4] IfInLoopPhiEmitter::emit_toplevel_phis start"); diff --git a/src/mir/loop_builder/if_lowering.rs b/src/mir/loop_builder/if_lowering.rs index 00d860a8..d699e943 100644 --- a/src/mir/loop_builder/if_lowering.rs +++ b/src/mir/loop_builder/if_lowering.rs @@ -203,7 +203,8 @@ impl<'a> LoopBuilder<'a> { // Phase 62-B: JoinIRIfPhiSelector箱化(-60行の簡潔化) let joinir_result = if crate::config::env::joinir_if_select_enabled() { if let Some(ref func) = self.parent_builder.current_function { - let selector = super::JoinIRIfPhiSelector::new(func, pre_branch_bb, carrier_names.clone()); + let selector = + super::JoinIRIfPhiSelector::new(func, pre_branch_bb, carrier_names.clone()); selector.try_lower() } else { super::JoinIRResult { diff --git a/src/mir/loop_builder/loop_form.rs b/src/mir/loop_builder/loop_form.rs index 8b42354c..c6943685 100644 --- a/src/mir/loop_builder/loop_form.rs +++ b/src/mir/loop_builder/loop_form.rs @@ -395,7 +395,7 @@ impl<'a> LoopBuilder<'a> { merge_block.add_instruction(MirInstruction::Phi { dst: phi_id, inputs: final_inputs, - type_hint: None, // Phase 63-6 + type_hint: None, // Phase 63-6 }); } } diff --git a/src/mir/loop_builder/phi_ops.rs b/src/mir/loop_builder/phi_ops.rs index d9af8709..9455c129 100644 --- a/src/mir/loop_builder/phi_ops.rs +++ b/src/mir/loop_builder/phi_ops.rs @@ -172,16 +172,17 @@ impl<'a> LoopFormOps for LoopBuilder<'a> { fn get_block_predecessors( &self, block: BasicBlockId, - ) -> std::collections::HashSet { + ) -> std::collections::BTreeSet { // 📦 Hotfix 6: Get actual CFG predecessors for PHI validation + // Phase 69-3: Changed to BTreeSet for determinism if let Some(ref func) = self.parent_builder.current_function { if let Some(bb) = func.blocks.get(&block) { bb.predecessors.clone() } else { - std::collections::HashSet::new() // Non-existent blocks have no predecessors + std::collections::BTreeSet::new() // Non-existent blocks have no predecessors } } else { - std::collections::HashSet::new() + std::collections::BTreeSet::new() } } diff --git a/src/mir/phi_core/loopform_builder.rs b/src/mir/phi_core/loopform_builder.rs index 2646372c..700a682f 100644 --- a/src/mir/phi_core/loopform_builder.rs +++ b/src/mir/phi_core/loopform_builder.rs @@ -869,10 +869,11 @@ pub trait LoopFormOps { /// 📦 Get actual CFG predecessors for a block (Hotfix 6: PHI input validation) /// Returns the set of blocks that actually branch to this block in the CFG. /// Used to validate exit PHI inputs against actual control flow. + /// Phase 69-3: Changed to BTreeSet for determinism fn get_block_predecessors( &self, block: BasicBlockId, - ) -> std::collections::HashSet; + ) -> std::collections::BTreeSet; /// Phase 26-A-4: ValueIdベースのパラメータ判定(型安全化) /// @@ -1029,9 +1030,10 @@ mod tests { fn get_block_predecessors( &self, _block: BasicBlockId, - ) -> std::collections::HashSet { + ) -> std::collections::BTreeSet { // MockOps: return empty set (no CFG in test) - std::collections::HashSet::new() + // Phase 69-3: Changed to BTreeSet for determinism + std::collections::BTreeSet::new() } /// Phase 26-A-4: ValueIdベースのパラメータ判定(Mock版) @@ -1197,8 +1199,9 @@ mod tests { fn get_block_predecessors( &self, _block: BasicBlockId, - ) -> std::collections::HashSet { - std::collections::HashSet::new() + ) -> std::collections::BTreeSet { + // Phase 69-3: Changed to BTreeSet for determinism + std::collections::BTreeSet::new() } /// Phase 26-A-4: ValueIdベースのパラメータ判定(Mock版・パラメータなし) diff --git a/src/mir/ssot/cf_common.rs b/src/mir/ssot/cf_common.rs index e1205770..9f4bc950 100644 --- a/src/mir/ssot/cf_common.rs +++ b/src/mir/ssot/cf_common.rs @@ -72,7 +72,11 @@ pub fn insert_phi_at_head_spanned( inputs.sort_by_key(|(bb, _)| bb.0); if let Some(bb) = f.get_block_mut(bb_id) { bb.insert_spanned_after_phis(SpannedInstruction { - inst: MirInstruction::Phi { dst, inputs, type_hint: None }, + inst: MirInstruction::Phi { + dst, + inputs, + type_hint: None, + }, span, }); } diff --git a/src/runner/json_v0_bridge/lowering/loop_.rs b/src/runner/json_v0_bridge/lowering/loop_.rs index 34a01a3e..90147d66 100644 --- a/src/runner/json_v0_bridge/lowering/loop_.rs +++ b/src/runner/json_v0_bridge/lowering/loop_.rs @@ -81,7 +81,8 @@ impl LoopFormOps for LoopFormJsonOps<'_> { fn get_block_predecessors( &self, block: BasicBlockId, - ) -> std::collections::HashSet { + ) -> std::collections::BTreeSet { + // Phase 69-3: Changed to BTreeSet for determinism self.f .blocks .get(&block) diff --git a/src/runner/json_v1_bridge.rs b/src/runner/json_v1_bridge.rs index 25be3685..385ab271 100644 --- a/src/runner/json_v1_bridge.rs +++ b/src/runner/json_v1_bridge.rs @@ -273,7 +273,7 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { block_ref.add_instruction(MirInstruction::Phi { dst: ValueId::new(dst), inputs: pairs, - type_hint: None, // Phase 63-6 + type_hint: None, // Phase 63-6 }); max_value_id = max_value_id.max(dst + 1); } diff --git a/src/runner/mir_json_v0.rs b/src/runner/mir_json_v0.rs index 827ae75c..8bd4e264 100644 --- a/src/runner/mir_json_v0.rs +++ b/src/runner/mir_json_v0.rs @@ -179,7 +179,7 @@ pub fn parse_mir_v0_to_module(json: &str) -> Result { block_ref.add_instruction(MirInstruction::Phi { dst: ValueId::new(dst), inputs: pairs, - type_hint: None, // Phase 63-6 + type_hint: None, // Phase 63-6 }); max_value_id = max_value_id.max(dst + 1); } diff --git a/src/tests/mir_joinir_if_select.rs b/src/tests/mir_joinir_if_select.rs index d77a40f2..58b89cec 100644 --- a/src/tests/mir_joinir_if_select.rs +++ b/src/tests/mir_joinir_if_select.rs @@ -673,7 +673,10 @@ mod tests { Some(MirType::Integer), "Expected type_hint to be Some(Integer) for IfSelectTest.simple (Const 10/20)" ); - eprintln!("✅ Phase 63-2: Type hint propagation successful: {:?}", type_hint); + eprintln!( + "✅ Phase 63-2: Type hint propagation successful: {:?}", + type_hint + ); } else { panic!("Expected Select instruction with type_hint"); }