Skip to content

Commit 60f5b00

Browse files
levkroppclaude
andcommitted
Fix: phi coalesce propagation creates overlapping register assignments
The phi coalesce step unconditionally copied register assignments from phi destinations to backedge sources without checking for conflicts. This created 5974 overlapping register assignments across SQLite, where two live values shared the same physical register simultaneously. Fix: check for interval conflicts before propagating phi coalesce assignments. Only coalesce when the backedge source's live interval doesn't overlap with any other value in the same register. Results: overlaps reduced from 5974 to 1523 (74% reduction). SQLite CREATE TABLE now returns rc=21 (SQLITE_MISUSE) instead of rc=103 (corrupted random value). Also adds CCC_VERIFY_REGALLOC=1 diagnostic that detects overlapping register assignments at compile time. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 78cf89a commit 60f5b00

1 file changed

Lines changed: 45 additions & 2 deletions

File tree

src/backend/regalloc.rs

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,10 +360,30 @@ pub fn allocate_registers(func: &IrFunction, config: &RegAllocConfig) -> RegAllo
360360
// Propagate phi coalesce assignments: backedge source values inherit
361361
// the register of their phi dest. This makes the backedge Copy a no-op
362362
// when both values share the same register.
363+
// Safety check: only propagate if the backedge source's interval doesn't
364+
// conflict with other values already assigned to the same register.
363365
for &(phi_dest, backedge_src) in &phi_coalesce {
364366
if let Some(&reg) = assignments.get(&phi_dest) {
365-
assignments.insert(backedge_src, reg);
366-
// No need to add to used_regs_set — the phi dest already did.
367+
// Find backedge_src's interval
368+
let src_interval = liveness.intervals.iter()
369+
.find(|iv| iv.value_id == backedge_src);
370+
if let Some(src_iv) = src_interval {
371+
// Check for conflicts with other values in the same register
372+
let has_conflict = liveness.intervals.iter().any(|iv| {
373+
if iv.value_id == backedge_src || iv.value_id == phi_dest { return false; }
374+
if let Some(&other_reg) = assignments.get(&iv.value_id) {
375+
other_reg.0 == reg.0 && iv.start < src_iv.end && src_iv.start < iv.end
376+
} else {
377+
false
378+
}
379+
});
380+
if !has_conflict {
381+
assignments.insert(backedge_src, reg);
382+
}
383+
} else {
384+
// No interval info — still safe to propagate (value might be dead)
385+
assignments.insert(backedge_src, reg);
386+
}
367387
}
368388
}
369389

@@ -479,6 +499,29 @@ pub fn allocate_registers(func: &IrFunction, config: &RegAllocConfig) -> RegAllo
479499
let mut used_regs: Vec<PhysReg> = used_regs_set.iter().map(|&r| PhysReg(r)).collect();
480500
used_regs.sort_by_key(|r| r.0);
481501

502+
// Verify: no two assigned values should have overlapping live intervals
503+
// in the same physical register.
504+
if std::env::var("CCC_VERIFY_REGALLOC").is_ok() {
505+
let mut reg_intervals: std::collections::HashMap<u8, Vec<(u32, u32, u32)>> = std::collections::HashMap::new();
506+
for iv in &liveness.intervals {
507+
if let Some(&reg) = assignments.get(&iv.value_id) {
508+
reg_intervals.entry(reg.0).or_default().push((iv.start, iv.end, iv.value_id));
509+
}
510+
}
511+
for (&reg_id, intervals) in &reg_intervals {
512+
for i in 0..intervals.len() {
513+
for j in (i+1)..intervals.len() {
514+
let (s1, e1, v1) = intervals[i];
515+
let (s2, e2, v2) = intervals[j];
516+
if s1 < e2 && s2 < e1 {
517+
eprintln!("[REGALLOC-OVERLAP] reg={} val{}[{}-{}] overlaps val{}[{}-{}]",
518+
reg_id, v1, s1, e1, v2, s2, e2);
519+
}
520+
}
521+
}
522+
}
523+
}
524+
482525
if std::env::var("CCC_DEBUG_REGALLOC").is_ok() && eligible.len() > 50 {
483526
let total_eligible = eligible.len();
484527
let total_assigned = assignments.len();

0 commit comments

Comments
 (0)