feat(phi): Option C実装完了 - 箱理論でPHI bug根本修正 + 完全共通化
🎯 **Option C - Box-First Design** **新規Box追加(2ファイル)**: - LocalScopeInspectorBox: 変数定義位置追跡(280行、13テスト) - LoopVarClassBox: 変数分類(Pinned/Carrier/BodyLocalExit/BodyLocalInternal)(220行、10テスト) **統合完了(3ファイル)**: - loop_snapshot_merge.rs: merge_exit_with_classification() でOption C分類適用 - loopform_builder.rs: build_exit_phis() でinspector統合、完全共通化達成 - loop_.rs (JSON Bridge): 重複コード削除、共通化された実装を使用 **技術的成果**: ✅ 266テスト通過(既存機能に影響なし) ✅ BodyLocalInternal変数の正しい分類(exit PHI生成スキップ) ✅ JSON/AST両経路の完全共通化(重複コード根絶) ✅ is_available_in_all() でPHI pred mismatch防止 **残課題**: - mir_funcscanner_skip_ws: 1テストのみ失敗(別変数で問題継続中) - 次: __mir__.log() でのトレース + 詳細調査 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -84,26 +84,38 @@ impl LocalScopeInspectorBox {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Check if a variable is defined in ALL of the specified blocks
|
||||
/// Check if a variable is AVAILABLE in ALL of the specified blocks
|
||||
///
|
||||
/// # Semantics (Important!)
|
||||
///
|
||||
/// This checks if the variable is "in scope" (available) at each block,
|
||||
/// NOT whether it was "newly defined" in that block.
|
||||
///
|
||||
/// - A variable is "available" if it's in the snapshot at that point
|
||||
/// - This includes both newly-defined variables AND inherited variables
|
||||
///
|
||||
/// For Option C PHI generation:
|
||||
/// - "available in all exit preds" → can generate exit PHI
|
||||
/// - "NOT available in all exit preds" → BodyLocalInternal, skip exit PHI
|
||||
///
|
||||
/// # Returns
|
||||
/// - `true` if the variable is defined in every block in `required_blocks`
|
||||
/// - `true` if the variable is available in every block in `required_blocks`
|
||||
/// - `false` if the variable is missing from any block, or doesn't exist
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// // Variable "ch" is only defined in block 5
|
||||
/// // Variable "ch" is only available in block 5
|
||||
/// inspector.record_definition("ch", BasicBlockId::new(5));
|
||||
///
|
||||
/// let exit_preds = vec![BasicBlockId::new(2), BasicBlockId::new(5)];
|
||||
/// assert!(!inspector.is_defined_in_all("ch", &exit_preds)); // false: missing block 2
|
||||
/// assert!(!inspector.is_available_in_all("ch", &exit_preds)); // false: missing block 2
|
||||
///
|
||||
/// // Variable "i" is defined in both blocks
|
||||
/// // Variable "i" is available in both blocks
|
||||
/// inspector.record_definition("i", BasicBlockId::new(2));
|
||||
/// inspector.record_definition("i", BasicBlockId::new(5));
|
||||
/// assert!(inspector.is_defined_in_all("i", &exit_preds)); // true: in all blocks
|
||||
/// assert!(inspector.is_available_in_all("i", &exit_preds)); // true: in all blocks
|
||||
/// ```
|
||||
pub fn is_defined_in_all(&self, var_name: &str, required_blocks: &[BasicBlockId]) -> bool {
|
||||
pub fn is_available_in_all(&self, var_name: &str, required_blocks: &[BasicBlockId]) -> bool {
|
||||
if let Some(defining_blocks) = self.var_definitions.get(var_name) {
|
||||
required_blocks.iter().all(|block| defining_blocks.contains(block))
|
||||
} else {
|
||||
@ -172,7 +184,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_defined_in_all_success() {
|
||||
fn test_is_available_in_all_success() {
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
|
||||
inspector.record_definition("i", BasicBlockId::new(2));
|
||||
@ -184,11 +196,11 @@ mod tests {
|
||||
BasicBlockId::new(5),
|
||||
];
|
||||
|
||||
assert!(inspector.is_defined_in_all("i", &required));
|
||||
assert!(inspector.is_available_in_all("i", &required));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_defined_in_all_failure() {
|
||||
fn test_is_available_in_all_failure() {
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
|
||||
// "ch" only defined in block 5
|
||||
@ -200,16 +212,16 @@ mod tests {
|
||||
];
|
||||
|
||||
// Should fail because block 2 is missing
|
||||
assert!(!inspector.is_defined_in_all("ch", &required));
|
||||
assert!(!inspector.is_available_in_all("ch", &required));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_defined_in_all_unknown_variable() {
|
||||
fn test_is_available_in_all_unknown_variable() {
|
||||
let inspector = LocalScopeInspectorBox::new();
|
||||
|
||||
let required = vec![BasicBlockId::new(2)];
|
||||
|
||||
assert!(!inspector.is_defined_in_all("unknown", &required));
|
||||
assert!(!inspector.is_available_in_all("unknown", &required));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -309,12 +321,12 @@ mod tests {
|
||||
let exit_preds = vec![block_2, block_5, block_7];
|
||||
|
||||
// i, n, s should be in all exit preds
|
||||
assert!(inspector.is_defined_in_all("i", &exit_preds));
|
||||
assert!(inspector.is_defined_in_all("n", &exit_preds));
|
||||
assert!(inspector.is_defined_in_all("s", &exit_preds));
|
||||
assert!(inspector.is_available_in_all("i", &exit_preds));
|
||||
assert!(inspector.is_available_in_all("n", &exit_preds));
|
||||
assert!(inspector.is_available_in_all("s", &exit_preds));
|
||||
|
||||
// ch should NOT be in all exit preds (missing block 2 and 7)
|
||||
assert!(!inspector.is_defined_in_all("ch", &exit_preds));
|
||||
assert!(!inspector.is_available_in_all("ch", &exit_preds));
|
||||
|
||||
// This means ch should NOT get an exit PHI!
|
||||
}
|
||||
|
||||
@ -238,7 +238,7 @@ impl LoopSnapshotMergeBox {
|
||||
|
||||
// Option C: Additional check - even Carrier/Pinned need definition check!
|
||||
// Carrier/Pinned だからといって、全 exit preds で定義されているとは限らない
|
||||
let is_in_all_preds = inspector.is_defined_in_all(&var_name, exit_preds);
|
||||
let is_in_all_preds = inspector.is_available_in_all(&var_name, exit_preds);
|
||||
|
||||
// exit PHI が不要な場合は skip
|
||||
if !class.needs_exit_phi() || !is_in_all_preds {
|
||||
|
||||
@ -186,7 +186,7 @@ impl LoopVarClassBox {
|
||||
|
||||
// Priority 3: Check if it's a body-local variable
|
||||
// Option C logic: Check if defined in ALL exit predecessors
|
||||
if inspector.is_defined_in_all(var_name, exit_preds) {
|
||||
if inspector.is_available_in_all(var_name, exit_preds) {
|
||||
LoopVarClass::BodyLocalExit
|
||||
} else {
|
||||
// ← Option C: Skip exit PHI for partial body-local variables!
|
||||
|
||||
@ -392,7 +392,7 @@ impl LoopFormBuilder {
|
||||
exit_id: BasicBlockId,
|
||||
branch_source_block: BasicBlockId,
|
||||
exit_snapshots: &[(BasicBlockId, HashMap<String, ValueId>)],
|
||||
inspector: &super::local_scope_inspector::LocalScopeInspectorBox,
|
||||
inspector: &mut super::local_scope_inspector::LocalScopeInspectorBox,
|
||||
) -> Result<(), String> {
|
||||
ops.set_current_block(exit_id)?;
|
||||
|
||||
@ -439,15 +439,9 @@ impl LoopFormBuilder {
|
||||
eprintln!("[DEBUG/exit_phi] Found {} body-local variables", body_local_names.len());
|
||||
}
|
||||
|
||||
// Add header values for body-local variables
|
||||
for var_name in &body_local_names {
|
||||
if let Some(header_value) = ops.get_variable_at_block(var_name, self.header_id) {
|
||||
header_vals.insert(var_name.clone(), header_value);
|
||||
if debug {
|
||||
eprintln!("[DEBUG/exit_phi] Added header value for body-local '{}': {:?}", var_name, header_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Option C: Body-local variables should NOT be added to header_vals!
|
||||
// They are defined inside the loop body, not in the header.
|
||||
// The inspector will correctly track which exit preds have them.
|
||||
|
||||
// 📦 Hotfix 6: Filter exit_snapshots to only include valid CFG predecessors
|
||||
let exit_preds = ops.get_block_predecessors(exit_id);
|
||||
@ -472,7 +466,34 @@ impl LoopFormBuilder {
|
||||
filtered_snapshots.push((*block_id, snapshot.clone()));
|
||||
}
|
||||
|
||||
// 3. Option C: merge_exit_with_classification() でPHI pred mismatch防止
|
||||
// 3. Option C: Record snapshots in inspector for availability checking
|
||||
// CRITICAL FIX: Record ALL exit pred snapshots, including header!
|
||||
|
||||
// Step 3-1: Record header variable definitions (pinned + carriers)
|
||||
// This must be done FIRST so that Option C can distinguish:
|
||||
// - Variables available in header → may need exit PHI if in all exit preds
|
||||
// - Variables NOT in header (BodyLocalInternal) → skip exit PHI
|
||||
for pinned in &self.pinned {
|
||||
inspector.record_definition(&pinned.name, self.header_id);
|
||||
}
|
||||
for carrier in &self.carriers {
|
||||
inspector.record_definition(&carrier.name, self.header_id);
|
||||
}
|
||||
|
||||
// Step 3-2: Record filtered exit snapshots
|
||||
for (block_id, snapshot) in &filtered_snapshots {
|
||||
inspector.record_snapshot(*block_id, snapshot);
|
||||
}
|
||||
|
||||
// Step 3-3: Record header snapshot if it's an exit predecessor
|
||||
if exit_preds.contains(&branch_source_block) {
|
||||
inspector.record_snapshot(branch_source_block, &header_vals);
|
||||
if debug {
|
||||
eprintln!("[DEBUG/exit_phi] Recorded header snapshot for block {:?}", branch_source_block);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Option C: merge_exit_with_classification() でPHI pred mismatch防止
|
||||
// pinned/carrier名リストを準備
|
||||
let pinned_names: Vec<String> = self.pinned.iter().map(|p| p.name.clone()).collect();
|
||||
let carrier_names: Vec<String> = self.carriers.iter().map(|c| c.name.clone()).collect();
|
||||
@ -637,26 +658,15 @@ pub fn build_exit_phis_for_control<O: LoopFormOps>(
|
||||
);
|
||||
}
|
||||
|
||||
// Option C: Build LocalScopeInspectorBox from exit_snapshots
|
||||
// Option C: Create inspector (build_exit_phis will populate it)
|
||||
use super::local_scope_inspector::LocalScopeInspectorBox;
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
|
||||
// Record header variable definitions (pinned + carriers)
|
||||
let header_id = loopform.header_id;
|
||||
for pinned in &loopform.pinned {
|
||||
inspector.record_definition(&pinned.name, header_id);
|
||||
}
|
||||
for carrier in &loopform.carriers {
|
||||
inspector.record_definition(&carrier.name, header_id);
|
||||
}
|
||||
|
||||
// Record all variable definitions from exit snapshots
|
||||
for (block_id, snapshot) in exit_snapshots {
|
||||
inspector.record_snapshot(*block_id, snapshot);
|
||||
}
|
||||
|
||||
// Delegate to Option C implementation
|
||||
loopform.build_exit_phis(ops, exit_id, branch_source_block, exit_snapshots, &inspector)
|
||||
// build_exit_phis() now handles all inspector setup internally:
|
||||
// - Records pinned/carrier definitions in header
|
||||
// - Records filtered exit snapshots
|
||||
// - Records header snapshot if it's an exit predecessor
|
||||
loopform.build_exit_phis(ops, exit_id, branch_source_block, exit_snapshots, &mut inspector)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user