Files
hakorune/src/mir/join_ir/verify_phi_reserved.rs

220 lines
6.6 KiB
Rust
Raw Normal View History

obs(joinir): Phase 72 - PHI Reserved Region Observation Complete ## Summary Observed PHI dst ValueId distribution to determine if verifier can enforce reserved region (0-99). **Conclusion: Verifier strengthening NOT recommended.** ## Key Finding PHI dst allocation does NOT architecturally respect reserved region: - PHI dst comes from `builder.next_value_id()` (host MirBuilder) - Reserved region (0-99) is a JoinValueSpace contract for JoinIR lowering - These are separate allocation pools with no enforcement mechanism - Current stability is accidental (ValueId allocation ordering) ## Evidence Manual verification (`apps/tests/loop_min_while.hako`): - PHI dst = %3 (ValueId(3)) ✅ in reserved region - Works because PHI allocated early in function (0-20 typical) - JoinValueSpace allocates high (100+, 1000+) - Accidental separation, not enforced ## Implementation Added observation infrastructure (debug-only): - `src/mir/join_ir/verify_phi_reserved.rs` (266 lines) - PHI dst observer with distribution analyzer - Region classifier (Reserved/Param/Local) - Human-readable report generator - Instrumentation at PHI allocation points: - loop_header_phi_builder.rs:94, 151 - Debug-only `observe_phi_dst()` calls ## Test Results - Unit tests: ✅ 4/4 PASS (verify_phi_reserved module) - Lib tests: ✅ 950/950 PASS, 56 ignored - Normalized tests: ✅ 54/54 PASS - Manual verification: ✅ PHI dst in 0-99 observed ## Decision: Document, Don't Enforce **Rationale**: 1. No architectural mechanism to enforce PHI dst ∈ [0, 99] 2. Adding verifier creates false assumptions about allocation order 3. Current system stable through separate pools (950/950 tests) 4. Future architectural fix possible (Phase 73+) but not urgent ## Documentation - PHASE_72_SUMMARY.md: Executive summary and implementation record - phase72-phi-reserved-observation.md: Detailed findings and analysis - CURRENT_TASK.md: Phase 72 completion entry ## Next Steps - Phase 73: Update architecture overview with Phase 72 findings - Optional: Explicit PHI reserved pool (future enhancement) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 03:23:02 +09:00
//! Phase 72: PHI Reserved Region Verifier
//!
//! This module observes and verifies that PHI dst ValueIds stay within
//! the reserved region (0-99) as documented in join_value_space.rs.
//!
//! ## Goal
//!
//! According to the doc, PHI dst should be in the "PHI Reserved (0-99)" region.
//! However, the actual PHI dst allocation comes from `builder.next_value_id()`
//! in loop_header_phi_builder.rs, not from JoinValueSpace.
//!
//! This verifier collects PHI dst values during tests and checks if they
//! fall within the expected reserved region.
use crate::mir::ValueId;
use std::collections::BTreeSet;
use std::sync::Mutex;
/// Global collector for PHI dst observations (debug-only)
static PHI_DST_OBSERVATIONS: Mutex<Option<BTreeSet<u32>>> = Mutex::new(None);
/// Enable PHI dst observation (call this before tests)
#[cfg(debug_assertions)]
pub fn enable_observation() {
let mut obs = PHI_DST_OBSERVATIONS.lock().unwrap();
*obs = Some(BTreeSet::new());
}
/// Observe a PHI dst ValueId
#[cfg(debug_assertions)]
pub fn observe_phi_dst(dst: ValueId) {
if let Ok(mut obs) = PHI_DST_OBSERVATIONS.lock() {
if let Some(ref mut set) = *obs {
set.insert(dst.0);
}
}
}
/// Get observation results and reset
#[cfg(debug_assertions)]
pub fn get_observations() -> Vec<u32> {
let mut obs = PHI_DST_OBSERVATIONS.lock().unwrap();
if let Some(ref mut set) = *obs {
let result = set.iter().copied().collect();
set.clear();
result
} else {
vec![]
}
}
/// Disable observation
#[cfg(debug_assertions)]
pub fn disable_observation() {
let mut obs = PHI_DST_OBSERVATIONS.lock().unwrap();
*obs = None;
}
/// Analyze PHI dst distribution
#[cfg(debug_assertions)]
pub fn analyze_distribution(observations: &[u32]) -> PhiDistributionReport {
use crate::mir::join_ir::lowering::join_value_space::{
PHI_RESERVED_MAX, PARAM_MIN, LOCAL_MIN,
};
let mut in_reserved = 0;
let mut in_param = 0;
let mut in_local = 0;
let mut min_val = u32::MAX;
let mut max_val = 0;
for &dst in observations {
min_val = min_val.min(dst);
max_val = max_val.max(dst);
if dst <= PHI_RESERVED_MAX {
in_reserved += 1;
} else if dst < PARAM_MIN {
// Between PHI_RESERVED_MAX+1 and PARAM_MIN-1 (gap region)
in_param += 1; // Count as "leaked into param region vicinity"
} else if dst < LOCAL_MIN {
in_param += 1;
} else {
in_local += 1;
}
}
PhiDistributionReport {
total: observations.len(),
in_reserved,
in_param,
in_local,
min_val: if observations.is_empty() {
None
} else {
Some(min_val)
},
max_val: if observations.is_empty() {
None
} else {
Some(max_val)
},
}
}
/// Report of PHI dst distribution
#[derive(Debug, Clone)]
pub struct PhiDistributionReport {
pub total: usize,
pub in_reserved: usize,
pub in_param: usize,
pub in_local: usize,
pub min_val: Option<u32>,
pub max_val: Option<u32>,
}
impl PhiDistributionReport {
/// Check if all PHI dsts are within reserved region (0-99)
pub fn is_all_reserved(&self) -> bool {
self.total > 0 && self.in_reserved == self.total
}
/// Get human-readable summary
pub fn summary(&self) -> String {
format!(
"PHI dst distribution: total={}, reserved(0-99)={}, param(100-999)={}, local(1000+)={}, range=[{}-{}]",
self.total,
self.in_reserved,
self.in_param,
self.in_local,
self.min_val.map_or("N/A".to_string(), |v| v.to_string()),
self.max_val.map_or("N/A".to_string(), |v| v.to_string())
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_analyze_distribution_empty() {
let report = analyze_distribution(&[]);
assert_eq!(report.total, 0);
assert_eq!(report.in_reserved, 0);
assert!(report.min_val.is_none());
assert!(report.max_val.is_none());
}
#[test]
fn test_analyze_distribution_all_reserved() {
let observations = vec![0, 5, 10, 50, 99];
let report = analyze_distribution(&observations);
assert_eq!(report.total, 5);
assert_eq!(report.in_reserved, 5);
assert_eq!(report.in_param, 0);
assert_eq!(report.in_local, 0);
assert!(report.is_all_reserved());
assert_eq!(report.min_val, Some(0));
assert_eq!(report.max_val, Some(99));
}
#[test]
fn test_analyze_distribution_mixed() {
let observations = vec![0, 50, 100, 500, 1000, 2000];
let report = analyze_distribution(&observations);
assert_eq!(report.total, 6);
assert_eq!(report.in_reserved, 2); // 0, 50
assert_eq!(report.in_param, 2); // 100, 500
assert_eq!(report.in_local, 2); // 1000, 2000
assert!(!report.is_all_reserved());
assert_eq!(report.min_val, Some(0));
assert_eq!(report.max_val, Some(2000));
}
#[test]
fn test_analyze_distribution_all_local() {
let observations = vec![1000, 1500, 2000];
let report = analyze_distribution(&observations);
assert_eq!(report.total, 3);
assert_eq!(report.in_reserved, 0);
assert_eq!(report.in_param, 0);
assert_eq!(report.in_local, 3);
assert!(!report.is_all_reserved());
}
}
#[cfg(test)]
mod observation_tests {
use super::*;
/// Phase 72-1: Observe PHI dst distribution in existing tests
///
/// This test runs BEFORE strengthening the verifier. It collects actual
/// PHI dst values and reports their distribution.
///
/// Expected outcomes:
/// - If all PHI dst in reserved (0-99) → proceed to strengthen verifier
/// - If some PHI dst outside reserved → document why and skip verifier
#[test]
fn test_phase72_observe_phi_dst_distribution() {
// Enable observation
enable_observation();
// The observations will come from loop_header_phi_builder
// when it allocates PHI dst during other tests running in parallel
// For this initial test, we just verify the mechanism works
let observations = get_observations();
let report = analyze_distribution(&observations);
eprintln!("\n[Phase 72] PHI dst observation report (initial):");
eprintln!(" {}", report.summary());
eprintln!(" All in reserved region: {}", report.is_all_reserved());
// This test always passes - it's for observation only
disable_observation();
}
}