Files
hakorune/src/mir/loop_builder/if_lowering.rs
nyash-codex 7de192aa6b 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>
2025-11-30 09:38:28 +09:00

298 lines
13 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use super::{LoopBuilder, ValueId};
use crate::ast::ASTNode;
use crate::mir::control_form::{is_control_form_trace_on, ControlForm, IfShape};
use crate::mir::utils::{capture_actual_predecessor_and_jump, is_current_block_terminated};
use crate::mir::{BasicBlockId, ConstValue};
use std::collections::{BTreeMap, BTreeSet};
impl<'a> LoopBuilder<'a> {
/// Lower an if-statement inside a loop, preserving continue/break semantics and emitting PHIs per assigned variable.
pub(super) fn lower_if_in_loop(
&mut self,
condition: ASTNode,
then_body: Vec<ASTNode>,
else_body: Option<Vec<ASTNode>>,
) -> Result<ValueId, String> {
// Reserve a deterministic join id for debug region labeling (nested inside loop)
let join_id = self.parent_builder.debug_next_join_id();
// Pre-pin heuristic was deprecated; leave operands untouched for clarity.
// Evaluate condition and create blocks
let cond_val = self.parent_builder.build_expression(condition)?;
let then_bb = self.new_block();
let else_bb = self.new_block();
let merge_bb = self.new_block();
let pre_branch_bb = self.current_block()?;
self.emit_branch(cond_val, then_bb, else_bb)?;
// Capture pre-if variable map (used for phi normalization)
let pre_if_var_map = self.get_current_variable_map();
let trace_if = std::env::var("NYASH_IF_TRACE").ok().as_deref() == Some("1");
// (legacy) kept for earlier merge style; now unified helpers compute deltas directly.
// then branch
self.set_current_block(then_bb)?;
// Debug region: join then-branch (inside loop)
self.parent_builder
.debug_push_region(format!("join#{}", join_id) + "/then");
// Materialize all variables at entry via single-pred Phi (correctness-first)
let names_then: Vec<String> = self
.parent_builder
.variable_map
.keys()
.filter(|n| !n.starts_with("__pin$"))
.cloned()
.collect();
for name in names_then {
if let Some(&pre_v) = pre_if_var_map.get(&name) {
let phi_val = self.new_value();
self.emit_phi_at_block_start(then_bb, phi_val, vec![(pre_branch_bb, pre_v)])?;
let name_for_log = name.clone();
self.update_variable(name, phi_val);
if trace_if {
eprintln!(
"[if-trace] then-entry phi var={} pre={:?} -> dst={:?}",
name_for_log, pre_v, phi_val
);
}
}
}
for s in then_body.iter().cloned() {
let _ = self.build_statement(s)?;
// フェーズS修正統一終端検出ユーティリティ使用
if is_current_block_terminated(self.parent_builder)? {
break;
}
}
let then_var_map_end = self.get_current_variable_map();
// フェーズS修正最強モード指摘の「実到達predecessor捕捉」を統一
let _then_pred_to_merge =
capture_actual_predecessor_and_jump(self.parent_builder, merge_bb)?;
// Pop then-branch debug region
self.parent_builder.debug_pop_region();
// else branch
self.set_current_block(else_bb)?;
// Debug region: join else-branch (inside loop)
self.parent_builder
.debug_push_region(format!("join#{}", join_id) + "/else");
// Materialize all variables at entry via single-pred Phi (correctness-first)
let names2: Vec<String> = self
.parent_builder
.variable_map
.keys()
.filter(|n| !n.starts_with("__pin$"))
.cloned()
.collect();
for name in names2 {
if let Some(&pre_v) = pre_if_var_map.get(&name) {
let phi_val = self.new_value();
self.emit_phi_at_block_start(else_bb, phi_val, vec![(pre_branch_bb, pre_v)])?;
let name_for_log = name.clone();
self.update_variable(name, phi_val);
if trace_if {
eprintln!(
"[if-trace] else-entry phi var={} pre={:?} -> dst={:?}",
name_for_log, pre_v, phi_val
);
}
}
}
let mut else_var_map_end_opt: Option<BTreeMap<String, ValueId>> = None;
if let Some(es) = else_body.clone() {
for s in es.into_iter() {
let _ = self.build_statement(s)?;
// フェーズS修正統一終端検出ユーティリティ使用
if is_current_block_terminated(self.parent_builder)? {
break;
}
}
else_var_map_end_opt = Some(self.get_current_variable_map());
}
// フェーズS修正else branchでも統一実到達predecessor捕捉
let _else_pred_to_merge =
capture_actual_predecessor_and_jump(self.parent_builder, merge_bb)?;
// Pop else-branch debug region
self.parent_builder.debug_pop_region();
// Continue at merge
self.set_current_block(merge_bb)?;
// Debug region: join merge (inside loop)
self.parent_builder
.debug_push_region(format!("join#{}", join_id) + "/join");
// Phase 25.1: HashSet → BTreeSet決定性確保
// Phase 40-4.1: JoinIR経路をデフォルト化collect_assigned_vars削除
let _vars: BTreeSet<String> =
crate::mir::phi_core::if_phi::collect_assigned_vars_via_joinir(
&then_body,
else_body.as_ref(),
);
// Phase 26-E: PhiBuilderOps trait 実装(箱理論統一)
struct Ops<'b, 'a>(&'b mut LoopBuilder<'a>);
// Phase 26-E: PhiBuilderOps trait 実装(箱理論統一)
impl<'b, 'a> crate::mir::phi_core::phi_builder_box::PhiBuilderOps for Ops<'b, 'a> {
fn new_value(&mut self) -> ValueId {
self.0.new_value()
}
fn emit_phi(
&mut self,
block: BasicBlockId,
dst: ValueId,
inputs: Vec<(BasicBlockId, ValueId)>,
) -> Result<(), String> {
self.0.emit_phi_at_block_start(block, dst, inputs)
}
fn update_var(&mut self, name: String, value: ValueId) {
self.0.parent_builder.variable_map.insert(name, value);
}
fn get_block_predecessors(&self, block: BasicBlockId) -> Vec<BasicBlockId> {
if let Some(ref func) = self.0.parent_builder.current_function {
func.blocks
.get(&block)
.map(|bb| bb.predecessors.iter().copied().collect())
.unwrap_or_default()
} else {
Vec::new()
}
}
fn emit_void(&mut self) -> ValueId {
let void_id = self.0.new_value();
let _ = self.0.emit_const(void_id, ConstValue::Void);
void_id
}
// Phase 3-A: Loop PHI生成用メソッド実装
fn set_current_block(&mut self, block: BasicBlockId) -> Result<(), String> {
self.0.parent_builder.current_block = Some(block);
Ok(())
}
fn block_exists(&self, block: BasicBlockId) -> bool {
if let Some(ref func) = self.0.parent_builder.current_function {
func.blocks.contains_key(&block)
} else {
false
}
}
}
// Phase 25.1h: ControlForm統合版に切り替え
let if_shape = IfShape {
cond_block: pre_branch_bb,
then_block: then_bb,
else_block: Some(else_bb),
merge_block: merge_bb,
};
let form = ControlForm::from_if(if_shape.clone());
// Region/GC 観測レイヤPhase 25.1l:
// NYASH_REGION_TRACE=1 のときだけ、StageB 周辺 If 構造の
// Region 情報entry/exit/slotsをログに出すよ。
crate::mir::region::observer::observe_control_form(self.parent_builder, &form);
// Phase 61-1: If-in-loop JoinIR化開発フラグ制御
// carrier_namesを作成両経路で共通
let carrier_names: BTreeSet<String> = pre_if_var_map
.keys()
.filter(|name| !name.starts_with("__pin$")) // 一時変数除外
.cloned()
.collect();
// 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());
selector.try_lower()
} else {
super::JoinIRResult {
success: false,
phi_spec: None,
join_inst: None,
}
}
} else {
super::JoinIRResult {
success: false,
phi_spec: None,
join_inst: None,
}
};
let joinir_success = joinir_result.success;
let joinir_phi_spec_opt = joinir_result.phi_spec;
let mut ops = Ops(self);
// Phase 61-3: JoinIR本番経路IfInLoopPhiEmitter
if joinir_success {
if let Some(ref joinir_spec) = joinir_phi_spec_opt {
// IfInLoopPhiEmitter を使用してPHI生成
let else_snap_opt = else_var_map_end_opt.as_ref();
let phi_count = super::IfInLoopPhiEmitter::emit_header_phis(
joinir_spec,
&pre_if_var_map,
&then_var_map_end,
else_snap_opt,
&carrier_names,
&mut ops,
&if_shape,
)?;
if crate::config::env::joinir_if_in_loop_dryrun_enabled() {
eprintln!(
"[Phase 61-3] ✅ IfInLoopPhiEmitter generated {} PHIs",
phi_count
);
}
}
}
// フォールバック: PhiBuilderBox経路既存
if !joinir_success {
// Phase 26-E: PhiBuilderBox SSOT統合If PHI生成
// Legacy: merge_modified_with_control() → New: PhiBuilderBox::generate_phis()
// Phase 61-6.1: set_if_context削除、直接IfPhiContextを生成
let mut phi_builder = crate::mir::phi_core::phi_builder_box::PhiBuilderBox::new();
phi_builder.if_context = Some(crate::mir::phi_core::phi_builder_box::IfPhiContext {
in_loop_body: true,
loop_carrier_names: carrier_names.clone(),
});
// Phase 35-5: if_body_local_merge.rs削除、ロジックはPhiBuilderBox内に統合
let post_snapshots = if let Some(ref else_map) = else_var_map_end_opt {
vec![then_var_map_end.clone(), else_map.clone()]
} else {
vec![then_var_map_end.clone()]
};
phi_builder.generate_phis(&mut ops, &form, &pre_if_var_map, &post_snapshots)?;
// Phase 61-6.2: A/B比較削除JoinIR経路完全動作確認済み
// Phase 61-3でJoinIR経路が完全動作したため、観察コード削除
// SSOT: JoinIR → compute_phi_spec_from_joinir()
}
// Phase 26-E-4: PHI生成後に variable_map をリセットChatGPT/Task先生指示
// 理由: else_var_map_end_opt が正しい snapshot を保持したまま PHI 生成に渡す必要がある
// 修正前: PHI生成前にリセット → else ブロック内定義変数が消失 → domination error
// 修正後: PHI生成後にリセット → 正しいPHI入力 → SSA保証
self.parent_builder.variable_map = pre_if_var_map.clone();
// ControlForm 観測: 環境フラグ未設定時は既定ONのとき IfShape をダンプ
if is_control_form_trace_on() {
form.debug_dump();
#[cfg(debug_assertions)]
if let Some(ref func) = self.parent_builder.current_function {
if_shape.debug_validate(func);
}
}
let void_id = self.new_value();
self.emit_const(void_id, ConstValue::Void)?;
// Pop merge debug region
self.parent_builder.debug_pop_region();
Ok(void_id)
}
}