feat(mir): Phase 69-3 Fix MIR non-determinism with BTreeSet
Replace HashSet with BTreeSet for CFG predecessors/successors: - BasicBlock.predecessors: HashSet → BTreeSet - BasicBlock.successors: HashSet → BTreeSet - LoopFormOps.get_block_predecessors(): returns BTreeSet - BasicBlock.dominates(): takes &[BTreeSet<BasicBlockId>] This ensures deterministic PHI generation and test stability. Test results: - loop_with_continue_and_break tests: now deterministic (3/3 same output) - loopform tests: 14/14 PASS (no regressions) - merge_exit_with_classification tests: 3/3 PASS Technical changes (6 files): - basic_block.rs: BTreeSet types + new() initialization - loopform_builder.rs: trait signature + 2 mock implementations - phi_ops.rs: return type - json_v0_bridge/loop_.rs: return type Same pattern as Phase 25.1 (MirFunction.blocks HashMap → BTreeMap). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -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<String, BTreeSet<BasicBlockId>> {
|
||||
let mut var_defs = BTreeMap::new();
|
||||
for var_name in inspector.all_variables() {
|
||||
let blocks: BTreeSet<BasicBlockId> = inspector
|
||||
.get_defining_blocks(&var_name)
|
||||
.into_iter()
|
||||
.collect();
|
||||
var_defs.insert(var_name, blocks);
|
||||
}
|
||||
var_defs
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)]));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user