## Summary Implemented fail-fast validation for PHI ordering and value resolution in strict mode. ## Changes ### P1-1: Strict mode for "PHI after terminator" - File: `src/llvm_py/phi_wiring/wiring.py::ensure_phi` - Behavior: `NYASH_LLVM_PHI_STRICT=1` → RuntimeError if PHI created after terminator - Default: Warning only (no regression) ### P1-2: Strict mode for "fallback 0" - File: `src/llvm_py/phi_wiring/wiring.py::wire_incomings` - Behavior: Strict mode forbids silent fallback to 0 (2 locations) - Location 1: Unresolvable incoming value - Location 2: Type coercion failure - Error messages point to next debug file: `llvm_builder.py::_value_at_end_i64` ### P1-3: Connect verify_phi_ordering() to execution path - File: `src/llvm_py/builders/function_lower.py` - Behavior: Verify PHI ordering after all instructions emitted - Debug mode: Shows "✅ All N blocks have correct PHI ordering" - Strict mode: Raises RuntimeError with block list if violations found ## Testing ✅ Test 1: strict=OFF - passes without errors ✅ Test 2: strict=ON - passes without errors (no violations in test fixtures) ✅ Test 3: debug mode - verify_phi_ordering() connected and running ## Scope - LLVM harness (Python) changes only - No new environment variables (uses existing 3 from Phase 277 P2) - No JoinIR/Rust changes (root fix is Phase 279) - Default behavior unchanged (strict mode opt-in) ## Next Steps - Phase 278: Remove deprecated env var support - Phase 279: Root fix - unify "2本のコンパイラ" pipelines 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
283 lines
9.7 KiB
Rust
283 lines
9.7 KiB
Rust
//! Phase 136 follow-up (Step 5/7): VariableContext extraction
|
|
//!
|
|
//! Consolidates variable name → ValueId mapping management:
|
|
//! - variable_map: String -> ValueId mapping (SSA conversion tracking)
|
|
//! - Used extensively by JoinIR lowering for carrier tracking
|
|
//! - Critical for PHI node generation in if/loop patterns
|
|
//!
|
|
//! ## Relationship with other contexts:
|
|
//! - **BindingContext**: String -> BindingId (binding identity, parallel to variable_map)
|
|
//! - **TypeContext**: ValueId -> MirType (type information for ValueIds)
|
|
//! - **ScopeContext**: Manages lexical scope frames with variable restoration
|
|
//! - **CoreContext**: Allocates ValueId via next_value()
|
|
//!
|
|
//! ## Design:
|
|
//! - variable_map tracks current SSA values for named variables
|
|
//! - Used by JoinIR CarrierInfo::from_variable_map() for loop carrier tracking
|
|
//! - PHI nodes in if/loop patterns update variable_map with merged values
|
|
//! - NYASH_TRACE_VARMAP debug feature visualizes variable_map changes
|
|
//!
|
|
//! ## JoinIR Integration:
|
|
//! - CarrierInfo::from_variable_map(): Extracts loop carriers from variable_map
|
|
//! - ExitLine contract: Ensures carriers are present in variable_map
|
|
//! - Pattern 2/3/4: Track carrier variables across loop iterations
|
|
//!
|
|
//! Phase 25.1: HashMap → BTreeMap for deterministic PHI generation
|
|
//! Phase 136 Step 5/7: Extraction into dedicated context
|
|
|
|
use crate::mir::ValueId;
|
|
use std::collections::BTreeMap;
|
|
|
|
/// Phase 136 Step 5/7: Variable context for variable name → ValueId mapping
|
|
///
|
|
/// Manages the mapping from variable names to their current SSA ValueId.
|
|
/// This is the core data structure for SSA conversion and variable tracking.
|
|
///
|
|
/// ## Key responsibilities:
|
|
/// - Maintain current variable_map (String -> ValueId)
|
|
/// - Provide lookup/insertion/removal operations
|
|
/// - Support JoinIR carrier tracking via CarrierInfo::from_variable_map()
|
|
/// - Enable NYASH_TRACE_VARMAP debugging of variable mappings
|
|
///
|
|
/// ## Implementation note:
|
|
/// - Uses BTreeMap for deterministic iteration (Phase 25.1 - PHI generation consistency)
|
|
/// - ValueId allocation is delegated to CoreContext.next_value()
|
|
/// - Parallel to BindingContext (which tracks BindingId instead of ValueId)
|
|
#[derive(Debug, Clone)]
|
|
pub struct VariableContext {
|
|
/// Variable name to ValueId mapping (for SSA conversion)
|
|
///
|
|
/// ## Usage patterns:
|
|
/// - Variable assignment: `variable_map["x"] = new_value_id`
|
|
/// - Variable access: `let value_id = variable_map["x"]`
|
|
/// - PHI merging: Update variable_map with merged PHI ValueId
|
|
/// - JoinIR carriers: Extract via CarrierInfo::from_variable_map(&variable_map)
|
|
///
|
|
/// ## Examples:
|
|
/// ```text
|
|
/// // Simple variable tracking:
|
|
/// variable_map["x"] = ValueId(5)
|
|
/// variable_map["sum"] = ValueId(10)
|
|
///
|
|
/// // After PHI merge in if-statement:
|
|
/// variable_map["result"] = ValueId(42) // PHI(%then_val, %else_val)
|
|
///
|
|
/// // Loop carrier tracking (Pattern 2/3/4):
|
|
/// variable_map["i"] = ValueId(7) // Loop variable
|
|
/// variable_map["acc"] = ValueId(11) // Accumulator (carrier)
|
|
/// ```
|
|
///
|
|
/// Phase 25.1: HashMap → BTreeMap for deterministic PHI generation
|
|
pub(super) variable_map: BTreeMap<String, ValueId>,
|
|
}
|
|
|
|
impl Default for VariableContext {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl VariableContext {
|
|
/// Create a new VariableContext with empty variable_map
|
|
pub fn new() -> Self {
|
|
Self {
|
|
variable_map: BTreeMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Lookup a variable's current ValueId
|
|
///
|
|
/// Returns None if the variable is not in scope or not yet assigned.
|
|
pub fn lookup(&self, name: &str) -> Option<ValueId> {
|
|
self.variable_map.get(name).copied()
|
|
}
|
|
|
|
/// Require a variable's ValueId (fail-fast variant of lookup)
|
|
///
|
|
/// Returns the ValueId if the variable exists, otherwise returns Err.
|
|
///
|
|
/// ## Use cases:
|
|
/// - Pattern6/7/8: Variable extraction with clear error messages
|
|
/// - JoinIR lowering: Carrier/invariant resolution
|
|
///
|
|
/// ## Example:
|
|
/// ```ignore
|
|
/// let i_init = builder.variable_ctx.require(&parts.i_var, "pattern7")?;
|
|
/// let s_host = builder.variable_ctx.require(&parts.s_var, "pattern7")?;
|
|
/// ```
|
|
///
|
|
/// Phase 272 P0.2 Refactoring: Eliminate variable_map.get().ok_or() boilerplate
|
|
pub fn require(&self, name: &str, context: &str) -> Result<ValueId, String> {
|
|
self.variable_map
|
|
.get(name)
|
|
.copied()
|
|
.ok_or_else(|| format!("[{}] Variable '{}' not found in variable_map", context, name))
|
|
}
|
|
|
|
/// Insert or update a variable's ValueId
|
|
///
|
|
/// ## Important notes:
|
|
/// - **__pin$ temporaries**: NEVER insert __pin$ prefixed names
|
|
/// (Step 5-5-F/G: __pin$ are transient compiler temporaries, not real variables)
|
|
/// - **SSA renaming**: Each assignment creates a new ValueId
|
|
/// - **PHI merging**: Update with merged PHI ValueId after if/loop
|
|
pub fn insert(&mut self, name: String, value_id: ValueId) {
|
|
self.variable_map.insert(name, value_id);
|
|
}
|
|
|
|
/// Remove a variable from the map
|
|
///
|
|
/// Returns the previous ValueId if the variable existed.
|
|
/// Used for scope restoration and cleanup.
|
|
pub fn remove(&mut self, name: &str) -> Option<ValueId> {
|
|
self.variable_map.remove(name)
|
|
}
|
|
|
|
/// Get immutable reference to the variable_map
|
|
///
|
|
/// ## Use cases:
|
|
/// - JoinIR: `CarrierInfo::from_variable_map(&variable_map)`
|
|
/// - PHI generation: Iterate over variables to detect changes
|
|
/// - Debugging: NYASH_TRACE_VARMAP visualization
|
|
pub fn variable_map(&self) -> &BTreeMap<String, ValueId> {
|
|
&self.variable_map
|
|
}
|
|
|
|
/// Get mutable reference to the variable_map
|
|
///
|
|
/// ## Use cases:
|
|
/// - Bulk operations (clone, swap, replace)
|
|
/// - Legacy code migration (temporary during Phase 136)
|
|
pub fn variable_map_mut(&mut self) -> &mut BTreeMap<String, ValueId> {
|
|
&mut self.variable_map
|
|
}
|
|
|
|
/// Check if a variable is currently mapped
|
|
pub fn contains(&self, name: &str) -> bool {
|
|
self.variable_map.contains_key(name)
|
|
}
|
|
|
|
/// Get the number of variables in the map
|
|
pub fn len(&self) -> usize {
|
|
self.variable_map.len()
|
|
}
|
|
|
|
/// Check if there are no variables in the map
|
|
pub fn is_empty(&self) -> bool {
|
|
self.variable_map.is_empty()
|
|
}
|
|
|
|
/// Clone the current variable_map (for snapshot/restore patterns)
|
|
///
|
|
/// ## Use cases:
|
|
/// - Before if-statement: Save pre-if variable_map
|
|
/// - Before loop: Save pre-loop variable_map
|
|
/// - PHI generation: Compare pre/post variable_map to detect changes
|
|
pub fn snapshot(&self) -> BTreeMap<String, ValueId> {
|
|
self.variable_map.clone()
|
|
}
|
|
|
|
/// Restore variable_map from a snapshot
|
|
///
|
|
/// ## Use cases:
|
|
/// - After if-then branch: Restore pre-if state before else branch
|
|
/// - Scope exit: Restore outer scope's variable_map
|
|
pub fn restore(&mut self, snapshot: BTreeMap<String, ValueId>) {
|
|
self.variable_map = snapshot;
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_variable_context_basic() {
|
|
let mut ctx = VariableContext::new();
|
|
assert!(ctx.is_empty());
|
|
assert_eq!(ctx.len(), 0);
|
|
|
|
let vid = ValueId::new(42);
|
|
ctx.insert("x".to_string(), vid);
|
|
assert_eq!(ctx.lookup("x"), Some(vid));
|
|
assert_eq!(ctx.len(), 1);
|
|
assert!(!ctx.is_empty());
|
|
|
|
ctx.remove("x");
|
|
assert_eq!(ctx.lookup("x"), None);
|
|
assert!(ctx.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_variable_context_contains() {
|
|
let mut ctx = VariableContext::new();
|
|
assert!(!ctx.contains("x"));
|
|
|
|
ctx.insert("x".to_string(), ValueId::new(1));
|
|
assert!(ctx.contains("x"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_variable_map_access() {
|
|
let mut ctx = VariableContext::new();
|
|
ctx.insert("a".to_string(), ValueId::new(10));
|
|
ctx.insert("b".to_string(), ValueId::new(20));
|
|
|
|
let map = ctx.variable_map();
|
|
assert_eq!(map.len(), 2);
|
|
assert_eq!(map.get("a"), Some(&ValueId::new(10)));
|
|
assert_eq!(map.get("b"), Some(&ValueId::new(20)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_snapshot_restore() {
|
|
let mut ctx = VariableContext::new();
|
|
ctx.insert("x".to_string(), ValueId::new(1));
|
|
ctx.insert("y".to_string(), ValueId::new(2));
|
|
|
|
// Take snapshot
|
|
let snapshot = ctx.snapshot();
|
|
assert_eq!(snapshot.len(), 2);
|
|
|
|
// Modify context
|
|
ctx.insert("z".to_string(), ValueId::new(3));
|
|
assert_eq!(ctx.len(), 3);
|
|
|
|
// Restore snapshot
|
|
ctx.restore(snapshot);
|
|
assert_eq!(ctx.len(), 2);
|
|
assert!(ctx.contains("x"));
|
|
assert!(ctx.contains("y"));
|
|
assert!(!ctx.contains("z"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_btree_deterministic_iteration() {
|
|
let mut ctx = VariableContext::new();
|
|
ctx.insert("z".to_string(), ValueId::new(3));
|
|
ctx.insert("a".to_string(), ValueId::new(1));
|
|
ctx.insert("m".to_string(), ValueId::new(2));
|
|
|
|
// BTreeMap should maintain sorted order
|
|
let keys: Vec<_> = ctx.variable_map().keys().cloned().collect();
|
|
assert_eq!(keys, vec!["a", "m", "z"]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ssa_renaming_pattern() {
|
|
let mut ctx = VariableContext::new();
|
|
|
|
// Initial assignment: x = 1
|
|
ctx.insert("x".to_string(), ValueId::new(1));
|
|
assert_eq!(ctx.lookup("x"), Some(ValueId::new(1)));
|
|
|
|
// SSA renaming: x = 2 (new ValueId)
|
|
ctx.insert("x".to_string(), ValueId::new(2));
|
|
assert_eq!(ctx.lookup("x"), Some(ValueId::new(2)));
|
|
|
|
// PHI merge: x = PHI(ValueId(2), ValueId(3))
|
|
ctx.insert("x".to_string(), ValueId::new(4));
|
|
assert_eq!(ctx.lookup("x"), Some(ValueId::new(4)));
|
|
}
|
|
}
|