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:
nyash-codex
2025-11-30 09:38:28 +09:00
parent 3387d9c1dc
commit 7de192aa6b
18 changed files with 286 additions and 1315 deletions

View File

@ -68,12 +68,13 @@ impl IfInLoopPhiEmitter {
if_shape: &IfShape,
) -> Result<usize, String> {
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<usize, String> {
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");

View File

@ -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 {

View File

@ -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
});
}
}

View File

@ -172,16 +172,17 @@ impl<'a> LoopFormOps for LoopBuilder<'a> {
fn get_block_predecessors(
&self,
block: BasicBlockId,
) -> std::collections::HashSet<BasicBlockId> {
) -> std::collections::BTreeSet<BasicBlockId> {
// 📦 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()
}
}