feat(joinir): Phase 59 - Ownership P3 plumbing (dev-only)
P3 (if-sum) パターン用の OwnershipPlan → lowering inputs 変換を追加。 Key changes: - plan_to_lowering.rs (+153 lines): - P3LoweringInputs struct (same structure as P2) - plan_to_p3_inputs() converter - 4 unit tests (multi-carrier, 5+, condition-only, relay-rejected) P3 specific features: - Multi-carrier support (sum, count, 5+ carriers) - Same Fail-Fast on relay_writes (Phase 59 scope) - Same CarrierRole discrimination (LoopState vs ConditionOnly) Integration tests: - test_phase59_ownership_p3_relay_failfast: Verifies relay detection - test_phase59_ownership_p3_loop_local_success: Verifies loop-local success Design: Perfect parallelism with P2 - Same struct layout (carriers, captures, condition_captures) - Same conversion logic (skip loop var, filter written vars) - Same error handling (Fail-Fast on relay) Tests: 946/946 PASS (16 ownership tests) All code under #[cfg(feature = "normalized_dev")] - zero impact on canonical. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -19,10 +19,11 @@
|
||||
//! 3. **Relay Propagation**: writes to ancestor-owned → relay up
|
||||
//! 4. **Capture Read-Only**: captures have no PHI at this scope
|
||||
//!
|
||||
//! # Phase 58 Status
|
||||
//! # Phase Status
|
||||
//!
|
||||
//! - Phase 57: Analyzer implemented (dev-only)
|
||||
//! - Phase 58: plan_to_lowering helper for P2 (analyzer-based testing only)
|
||||
//! - Phase 59: plan_to_lowering helper for P3 (if-sum patterns)
|
||||
|
||||
mod types;
|
||||
mod analyzer;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
//! Convert OwnershipPlan to P2 lowering inputs.
|
||||
//! Convert OwnershipPlan to P2/P3 lowering inputs.
|
||||
//!
|
||||
//! Phase 58: dev-only, P2 only.
|
||||
//! Phase 58: P2 conversion helper
|
||||
//! Phase 59: P3 conversion helper
|
||||
|
||||
use super::OwnershipPlan;
|
||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierInit, CarrierRole, CarrierVar};
|
||||
@ -16,6 +17,18 @@ pub struct P2LoweringInputs {
|
||||
pub condition_captures: Vec<String>,
|
||||
}
|
||||
|
||||
/// Result of converting OwnershipPlan for P3 (if-sum) lowering
|
||||
#[derive(Debug)]
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
pub struct P3LoweringInputs {
|
||||
/// Carriers derived from owned_vars (is_written=true)
|
||||
pub carriers: Vec<CarrierVar>,
|
||||
/// Captured variables (read-only)
|
||||
pub captures: Vec<String>,
|
||||
/// Condition captures (used in if conditions)
|
||||
pub condition_captures: Vec<String>,
|
||||
}
|
||||
|
||||
/// Convert OwnershipPlan to P2 lowering inputs.
|
||||
///
|
||||
/// # Errors
|
||||
@ -76,6 +89,69 @@ pub fn plan_to_p2_inputs(
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert OwnershipPlan to P3 (if-sum) lowering inputs.
|
||||
///
|
||||
/// P3 patterns have multiple carriers (sum, count, etc.) updated conditionally.
|
||||
/// Logic is same as P2 - relay_writes are rejected.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns Err if relay_writes is non-empty (Phase 59 scope limitation).
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
pub fn plan_to_p3_inputs(
|
||||
plan: &OwnershipPlan,
|
||||
loop_var: &str,
|
||||
) -> Result<P3LoweringInputs, String> {
|
||||
// Fail-Fast: relay_writes not supported in Phase 59
|
||||
if !plan.relay_writes.is_empty() {
|
||||
return Err(format!(
|
||||
"Phase 59 limitation: relay_writes not yet supported for P3. Found: {:?}",
|
||||
plan.relay_writes.iter().map(|r| &r.name).collect::<Vec<_>>()
|
||||
));
|
||||
}
|
||||
|
||||
let mut carriers = Vec::new();
|
||||
|
||||
for var in &plan.owned_vars {
|
||||
// Skip loop variable (pinned, handled separately)
|
||||
if var.name == loop_var {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only written vars become carriers
|
||||
if !var.is_written {
|
||||
continue;
|
||||
}
|
||||
|
||||
let role = if var.is_condition_only {
|
||||
CarrierRole::ConditionOnly
|
||||
} else {
|
||||
CarrierRole::LoopState
|
||||
};
|
||||
|
||||
carriers.push(CarrierVar {
|
||||
name: var.name.clone(),
|
||||
role,
|
||||
init: CarrierInit::FromHost, // Default (Phase 228)
|
||||
host_id: crate::mir::ValueId(0), // Placeholder - not used in dev analysis
|
||||
join_id: None,
|
||||
});
|
||||
}
|
||||
|
||||
let captures: Vec<String> = plan.captures.iter().map(|c| c.name.clone()).collect();
|
||||
|
||||
let condition_captures: Vec<String> = plan
|
||||
.condition_captures
|
||||
.iter()
|
||||
.map(|c| c.name.clone())
|
||||
.collect();
|
||||
|
||||
Ok(P3LoweringInputs {
|
||||
carriers,
|
||||
captures,
|
||||
condition_captures,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -196,4 +272,134 @@ mod tests {
|
||||
assert_eq!(inputs.condition_captures.len(), 1);
|
||||
assert_eq!(inputs.condition_captures[0], "limit");
|
||||
}
|
||||
|
||||
// Phase 59: P3 conversion tests
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
fn test_p3_multi_carrier_conversion() {
|
||||
// P3 if-sum pattern: sum, count, i all loop-local
|
||||
let mut plan = OwnershipPlan::new(ScopeId(1));
|
||||
plan.owned_vars.push(ScopeOwnedVar {
|
||||
name: "i".to_string(),
|
||||
is_written: true,
|
||||
is_condition_only: false,
|
||||
});
|
||||
plan.owned_vars.push(ScopeOwnedVar {
|
||||
name: "sum".to_string(),
|
||||
is_written: true,
|
||||
is_condition_only: false,
|
||||
});
|
||||
plan.owned_vars.push(ScopeOwnedVar {
|
||||
name: "count".to_string(),
|
||||
is_written: true,
|
||||
is_condition_only: false,
|
||||
});
|
||||
|
||||
let inputs = plan_to_p3_inputs(&plan, "i").unwrap();
|
||||
|
||||
// i skipped, sum and count become carriers
|
||||
assert_eq!(inputs.carriers.len(), 2);
|
||||
assert!(inputs.carriers.iter().any(|c| c.name == "sum"));
|
||||
assert!(inputs.carriers.iter().any(|c| c.name == "count"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
fn test_p3_five_plus_carriers() {
|
||||
// Selfhost P3 pattern: 5+ carriers
|
||||
let mut plan = OwnershipPlan::new(ScopeId(1));
|
||||
plan.owned_vars
|
||||
.push(ScopeOwnedVar {
|
||||
name: "i".to_string(),
|
||||
is_written: true,
|
||||
is_condition_only: false,
|
||||
});
|
||||
plan.owned_vars.push(ScopeOwnedVar {
|
||||
name: "total".to_string(),
|
||||
is_written: true,
|
||||
is_condition_only: false,
|
||||
});
|
||||
plan.owned_vars.push(ScopeOwnedVar {
|
||||
name: "valid".to_string(),
|
||||
is_written: true,
|
||||
is_condition_only: false,
|
||||
});
|
||||
plan.owned_vars.push(ScopeOwnedVar {
|
||||
name: "error".to_string(),
|
||||
is_written: true,
|
||||
is_condition_only: false,
|
||||
});
|
||||
plan.owned_vars.push(ScopeOwnedVar {
|
||||
name: "warn".to_string(),
|
||||
is_written: true,
|
||||
is_condition_only: false,
|
||||
});
|
||||
plan.owned_vars.push(ScopeOwnedVar {
|
||||
name: "info".to_string(),
|
||||
is_written: true,
|
||||
is_condition_only: false,
|
||||
});
|
||||
|
||||
let inputs = plan_to_p3_inputs(&plan, "i").unwrap();
|
||||
|
||||
// 5 carriers (excluding loop var)
|
||||
assert_eq!(inputs.carriers.len(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
fn test_p3_condition_only_role() {
|
||||
let mut plan = OwnershipPlan::new(ScopeId(1));
|
||||
plan.owned_vars.push(ScopeOwnedVar {
|
||||
name: "i".to_string(),
|
||||
is_written: true,
|
||||
is_condition_only: false,
|
||||
});
|
||||
plan.owned_vars.push(ScopeOwnedVar {
|
||||
name: "is_valid".to_string(),
|
||||
is_written: true,
|
||||
is_condition_only: true, // Used only in if condition
|
||||
});
|
||||
plan.owned_vars.push(ScopeOwnedVar {
|
||||
name: "sum".to_string(),
|
||||
is_written: true,
|
||||
is_condition_only: false,
|
||||
});
|
||||
|
||||
let inputs = plan_to_p3_inputs(&plan, "i").unwrap();
|
||||
|
||||
let is_valid = inputs
|
||||
.carriers
|
||||
.iter()
|
||||
.find(|c| c.name == "is_valid")
|
||||
.unwrap();
|
||||
assert_eq!(is_valid.role, CarrierRole::ConditionOnly);
|
||||
|
||||
let sum = inputs.carriers.iter().find(|c| c.name == "sum").unwrap();
|
||||
assert_eq!(sum.role, CarrierRole::LoopState);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
fn test_p3_relay_rejected() {
|
||||
let mut plan = OwnershipPlan::new(ScopeId(1));
|
||||
plan.owned_vars.push(ScopeOwnedVar {
|
||||
name: "i".to_string(),
|
||||
is_written: true,
|
||||
is_condition_only: false,
|
||||
});
|
||||
// sum is written but owned by outer scope -> relay
|
||||
plan.relay_writes.push(RelayVar {
|
||||
name: "sum".to_string(),
|
||||
owner_scope: ScopeId(0),
|
||||
relay_path: vec![],
|
||||
});
|
||||
|
||||
let result = plan_to_p3_inputs(&plan, "i");
|
||||
assert!(result.is_err());
|
||||
assert!(result
|
||||
.unwrap_err()
|
||||
.contains("relay_writes not yet supported for P3"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user