feat(joinir): Phase 64 - Ownership P3 production integration (dev-only)
P3(if-sum) 本番ルートに OwnershipPlan 解析を接続(analysis-only)。 Key changes: - ast_analyzer.rs: Added analyze_loop() helper - Loop-specific analysis API for production integration - build_plan_for_scope() helper for single-scope extraction - pattern3_with_if_phi.rs: P3 production integration - OwnershipPlan analysis after ConditionEnv building - check_ownership_plan_consistency() for validation - Feature-gated #[cfg(feature = "normalized_dev")] Consistency checks: - Fail-Fast: Multi-hop relay (relay_path.len() > 1) - Warn-only: Carrier set mismatch (order SSOT deferred) - Warn-only: Condition captures (some patterns have extras) Tests: 49/49 PASS (2 new Phase 64 tests) - test_phase64_p3_ownership_prod_integration - test_phase64_p3_multihop_relay_detection - Zero regressions Design: Analysis-only, no behavior change - Integration point: After ConditionEnv, before JoinIR lowering - Dev-only validation for future SSOT migration Next: Phase 65+ - Carrier order SSOT, owner-based init 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -142,6 +142,54 @@ impl MirBuilder {
|
||||
);
|
||||
}
|
||||
|
||||
// Phase 64: Ownership analysis (dev-only, analysis-only)
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
{
|
||||
use crate::mir::join_ir::ownership::analyze_loop;
|
||||
|
||||
// Collect parent-defined variables from function scope
|
||||
// For now, use all variables in variable_map except loop_var
|
||||
let parent_defined: Vec<String> = self
|
||||
.variable_map
|
||||
.keys()
|
||||
.filter(|name| *name != &loop_var_name)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
match analyze_loop(condition, body, &parent_defined) {
|
||||
Ok(plan) => {
|
||||
// Convert ConditionBinding Vec to BTreeSet<String> for consistency check
|
||||
let condition_binding_names: std::collections::BTreeSet<String> =
|
||||
condition_bindings.iter().map(|b| b.name.clone()).collect();
|
||||
|
||||
// Run consistency checks
|
||||
if let Err(e) =
|
||||
check_ownership_plan_consistency(&plan, &ctx.carrier_info, &condition_binding_names)
|
||||
{
|
||||
eprintln!("[phase64/ownership] Consistency check failed: {}", e);
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
trace::trace().debug(
|
||||
"pattern3/if-sum",
|
||||
&format!(
|
||||
"OwnershipPlan analysis succeeded: {} owned vars, {} relay writes, {} captures",
|
||||
plan.owned_vars.len(),
|
||||
plan.relay_writes.len(),
|
||||
plan.captures.len()
|
||||
),
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"[phase64/ownership] Analysis failed (continuing with legacy): {}",
|
||||
e
|
||||
);
|
||||
// Don't fail - analysis is optional in Phase 64
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call AST-based if-sum lowerer with ConditionEnv
|
||||
let (join_module, fragment_meta) =
|
||||
lower_if_sum_pattern(condition, if_stmt, body, &cond_env, &mut join_value_space)?;
|
||||
@ -250,3 +298,73 @@ impl MirBuilder {
|
||||
|
||||
// Phase 242-EX-A: lower_pattern3_legacy removed - all patterns now use AST-based lowering
|
||||
}
|
||||
|
||||
/// Phase 64: Ownership plan consistency checks (dev-only)
|
||||
///
|
||||
/// Validates OwnershipPlan against existing CarrierInfo and ConditionBindings.
|
||||
/// This is analysis-only - no behavior change.
|
||||
///
|
||||
/// # Checks
|
||||
///
|
||||
/// 1. **Multi-hop relay rejection**: `relay_path.len() > 1` → Err (out of scope)
|
||||
/// 2. **Carrier set consistency**: plan carriers vs existing carriers (warn-only)
|
||||
/// 3. **Condition captures consistency**: plan captures vs condition bindings (warn-only)
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
fn check_ownership_plan_consistency(
|
||||
plan: &crate::mir::join_ir::ownership::OwnershipPlan,
|
||||
carrier_info: &crate::mir::join_ir::lowering::carrier_info::CarrierInfo,
|
||||
condition_bindings: &std::collections::BTreeSet<String>,
|
||||
) -> Result<(), String> {
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
// Check 1: Multi-hop relay is rejected (Fail-Fast)
|
||||
for relay in &plan.relay_writes {
|
||||
if relay.relay_path.len() > 1 {
|
||||
return Err(format!(
|
||||
"Phase 64 limitation: multi-hop relay not supported. Variable '{}' has relay path length {}",
|
||||
relay.name, relay.relay_path.len()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Check 2: Carrier set consistency (warn-only, order SSOT deferred)
|
||||
let plan_carriers: BTreeSet<String> = plan
|
||||
.owned_vars
|
||||
.iter()
|
||||
.filter(|v| v.is_written)
|
||||
.map(|v| v.name.clone())
|
||||
.collect();
|
||||
|
||||
let existing_carriers: BTreeSet<String> = carrier_info
|
||||
.carriers
|
||||
.iter()
|
||||
.map(|c| c.name.clone())
|
||||
.collect();
|
||||
|
||||
if plan_carriers != existing_carriers {
|
||||
eprintln!("[phase64/ownership] Carrier set mismatch (warn-only, order SSOT deferred):");
|
||||
eprintln!(" OwnershipPlan carriers: {:?}", plan_carriers);
|
||||
eprintln!(" Existing carriers: {:?}", existing_carriers);
|
||||
// Don't fail - just warn (order SSOT not yet implemented)
|
||||
}
|
||||
|
||||
// Check 3: Condition captures consistency (warn-only)
|
||||
let plan_cond_captures: BTreeSet<String> = plan
|
||||
.condition_captures
|
||||
.iter()
|
||||
.map(|c| c.name.clone())
|
||||
.collect();
|
||||
|
||||
if !plan_cond_captures.is_subset(condition_bindings) {
|
||||
let extra: Vec<_> = plan_cond_captures
|
||||
.difference(condition_bindings)
|
||||
.collect();
|
||||
eprintln!(
|
||||
"[phase64/ownership] Extra condition captures in plan (warn-only): {:?}",
|
||||
extra
|
||||
);
|
||||
// Warn only - this might be expected in some cases
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -613,6 +613,139 @@ impl Default for AstOwnershipAnalyzer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 64: Analyze a single loop (condition + body) with parent context.
|
||||
///
|
||||
/// This helper is designed for P3 production integration. It creates a temporary
|
||||
/// function scope for parent-defined variables, then analyzes the loop scope.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `condition` - Loop condition AST node
|
||||
/// * `body` - Loop body statements
|
||||
/// * `parent_defined` - Variables defined in parent scope (function params/locals)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// OwnershipPlan for the loop scope only (not the temporary function scope).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// // loop(i < 10) { local sum=0; sum=sum+1; i=i+1; }
|
||||
/// let condition = /* i < 10 */;
|
||||
/// let body = vec![/* local sum=0; sum=sum+1; i=i+1; */];
|
||||
/// let parent_defined = vec![];
|
||||
/// let plan = analyze_loop(&condition, &body, &parent_defined)?;
|
||||
/// // plan.owned_vars contains: sum (is_written=true), i (is_written=true)
|
||||
/// ```
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
pub fn analyze_loop(
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
parent_defined: &[String],
|
||||
) -> Result<OwnershipPlan, String> {
|
||||
let mut analyzer = AstOwnershipAnalyzer::new();
|
||||
|
||||
// Create temporary function scope for parent context
|
||||
let parent_scope = analyzer.alloc_scope(ScopeKind::Function, None);
|
||||
for var in parent_defined {
|
||||
analyzer
|
||||
.scopes
|
||||
.get_mut(&parent_scope)
|
||||
.unwrap()
|
||||
.defined
|
||||
.insert(var.clone());
|
||||
}
|
||||
|
||||
// Create loop scope
|
||||
let loop_scope = analyzer.alloc_scope(ScopeKind::Loop, Some(parent_scope));
|
||||
|
||||
// Analyze condition (with is_condition=true flag)
|
||||
analyzer.analyze_node(condition, loop_scope, true)?;
|
||||
|
||||
// Analyze body statements
|
||||
for stmt in body {
|
||||
analyzer.analyze_node(stmt, loop_scope, false)?;
|
||||
}
|
||||
|
||||
// Propagate to parent
|
||||
analyzer.propagate_to_parent(loop_scope);
|
||||
|
||||
// Build plan for loop only
|
||||
analyzer.build_plan_for_scope(loop_scope)
|
||||
}
|
||||
|
||||
impl AstOwnershipAnalyzer {
|
||||
/// Phase 64: Build OwnershipPlan for a specific scope (helper for analyze_loop).
|
||||
///
|
||||
/// This is a private helper used by `analyze_loop()` to extract a single
|
||||
/// scope's OwnershipPlan without building plans for all scopes.
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
fn build_plan_for_scope(&self, scope_id: ScopeId) -> Result<OwnershipPlan, String> {
|
||||
let scope = self
|
||||
.scopes
|
||||
.get(&scope_id)
|
||||
.ok_or_else(|| format!("Scope {:?} not found", scope_id))?;
|
||||
|
||||
let mut plan = OwnershipPlan::new(scope_id);
|
||||
|
||||
// Collect owned vars
|
||||
for name in &scope.defined {
|
||||
let is_written = scope.writes.contains(name);
|
||||
let is_condition_only = is_written && scope.condition_reads.contains(name);
|
||||
plan.owned_vars.push(ScopeOwnedVar {
|
||||
name: name.clone(),
|
||||
is_written,
|
||||
is_condition_only,
|
||||
});
|
||||
}
|
||||
|
||||
// Collect relay writes
|
||||
for name in &scope.writes {
|
||||
if scope.defined.contains(name) {
|
||||
continue;
|
||||
}
|
||||
if let Some((owner_scope, relay_path)) = self.find_owner(scope_id, name) {
|
||||
plan.relay_writes.push(RelayVar {
|
||||
name: name.clone(),
|
||||
owner_scope,
|
||||
relay_path,
|
||||
});
|
||||
} else {
|
||||
return Err(format!(
|
||||
"AstOwnershipAnalyzer: relay write '{}' in scope {:?} has no owner",
|
||||
name, scope_id
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Collect captures
|
||||
for name in &scope.reads {
|
||||
if scope.defined.contains(name) || scope.writes.contains(name) {
|
||||
continue;
|
||||
}
|
||||
if let Some((owner_scope, _)) = self.find_owner(scope_id, name) {
|
||||
plan.captures.push(CapturedVar {
|
||||
name: name.clone(),
|
||||
owner_scope,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Collect condition captures
|
||||
for cap in &plan.captures {
|
||||
if scope.condition_reads.contains(&cap.name) {
|
||||
plan.condition_captures.push(cap.clone());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
plan.verify_invariants()?;
|
||||
|
||||
Ok(plan)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user