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:
nyash-codex
2025-12-12 18:45:08 +09:00
parent fcf85313e5
commit 6aba138950
5 changed files with 521 additions and 5 deletions

View File

@ -1241,3 +1241,174 @@ fn test_phase58_ownership_p2_comparison() {
eprintln!("[phase58/test] Phase 58 conversion verified: sum correctly extracted as carrier");
}
/// Phase 59: P3 with outer-owned carriers (relay case) should fail-fast
#[test]
#[cfg(feature = "normalized_dev")]
fn test_phase59_ownership_p3_relay_failfast() {
use nyash_rust::mir::join_ir::ownership::{plan_to_p3_inputs, OwnershipAnalyzer};
use serde_json::json;
// P3 where sum/count are defined OUTSIDE the loop -> relay
let json = json!({
"functions": [{
"name": "main",
"params": [],
"body": {
"kind": "Block",
"statements": [
{"kind": "Local", "name": "sum", "init": {"kind": "Const", "value": 0}},
{"kind": "Local", "name": "count", "init": {"kind": "Const", "value": 0}},
{"kind": "Local", "name": "i", "init": {"kind": "Const", "value": 0}},
{
"kind": "Loop",
"condition": {
"kind": "BinaryOp", "op": "Lt",
"lhs": {"kind": "Var", "name": "i"},
"rhs": {"kind": "Const", "value": 10}
},
"body": {
"kind": "Block",
"statements": [
{
"kind": "If",
"condition": {
"kind": "BinaryOp", "op": "Gt",
"lhs": {"kind": "Var", "name": "i"},
"rhs": {"kind": "Const", "value": 0}
},
"then": {
"kind": "Block",
"statements": [
{"kind": "Assign", "target": "sum", "value": {
"kind": "BinaryOp", "op": "Add",
"lhs": {"kind": "Var", "name": "sum"},
"rhs": {"kind": "Var", "name": "i"}
}},
{"kind": "Assign", "target": "count", "value": {
"kind": "BinaryOp", "op": "Add",
"lhs": {"kind": "Var", "name": "count"},
"rhs": {"kind": "Const", "value": 1}
}}
]
}
},
{"kind": "Assign", "target": "i", "value": {
"kind": "BinaryOp", "op": "Add",
"lhs": {"kind": "Var", "name": "i"},
"rhs": {"kind": "Const", "value": 1}
}}
]
}
}
]
}
}]
});
let mut analyzer = OwnershipAnalyzer::new();
let plans = analyzer.analyze_json(&json).expect("analysis should succeed");
// Find loop plan
let loop_plan = plans
.iter()
.find(|p| !p.relay_writes.is_empty())
.expect("loop should have relay_writes for sum/count");
// Verify relay_writes contains sum and count
assert!(loop_plan.relay_writes.iter().any(|r| r.name == "sum"));
assert!(loop_plan.relay_writes.iter().any(|r| r.name == "count"));
// plan_to_p3_inputs should fail
let result = plan_to_p3_inputs(loop_plan, "i");
assert!(result.is_err(), "Should fail-fast on relay_writes");
assert!(
result.unwrap_err().contains("relay_writes not yet supported for P3"),
"Error should mention P3 relay limitation"
);
eprintln!("[phase59/test] P3 relay fail-fast verified");
}
/// Phase 59: P3 with loop-local carriers should succeed
#[test]
#[cfg(feature = "normalized_dev")]
fn test_phase59_ownership_p3_loop_local_success() {
use nyash_rust::mir::join_ir::ownership::{plan_to_p3_inputs, OwnershipAnalyzer};
use serde_json::json;
// P3 where sum/count are defined INSIDE the loop -> no relay
let json = json!({
"functions": [{
"name": "main",
"params": [],
"body": {
"kind": "Loop",
"condition": {"kind": "Const", "value": true},
"body": {
"kind": "Block",
"statements": [
{"kind": "Local", "name": "i", "init": {"kind": "Const", "value": 0}},
{"kind": "Local", "name": "sum", "init": {"kind": "Const", "value": 0}},
{"kind": "Local", "name": "count", "init": {"kind": "Const", "value": 0}},
{
"kind": "If",
"condition": {
"kind": "BinaryOp", "op": "Gt",
"lhs": {"kind": "Var", "name": "i"},
"rhs": {"kind": "Const", "value": 0}
},
"then": {
"kind": "Block",
"statements": [
{"kind": "Assign", "target": "sum", "value": {
"kind": "BinaryOp", "op": "Add",
"lhs": {"kind": "Var", "name": "sum"},
"rhs": {"kind": "Var", "name": "i"}
}},
{"kind": "Assign", "target": "count", "value": {
"kind": "BinaryOp", "op": "Add",
"lhs": {"kind": "Var", "name": "count"},
"rhs": {"kind": "Const", "value": 1}
}}
]
}
},
{"kind": "Break"}
]
}
}
}]
});
let mut analyzer = OwnershipAnalyzer::new();
let plans = analyzer.analyze_json(&json).expect("analysis should succeed");
// Find loop plan with owned vars
let loop_plan = plans
.iter()
.find(|p| p.owned_vars.iter().any(|v| v.name == "sum"))
.expect("loop should own sum");
// No relay
assert!(
loop_plan.relay_writes.is_empty(),
"No relay for loop-local vars"
);
// plan_to_p3_inputs should succeed
let inputs = plan_to_p3_inputs(loop_plan, "i").expect("Should succeed");
eprintln!("[phase59/test] P3 inputs: {:?}", inputs);
// sum and count should be carriers
assert!(inputs.carriers.iter().any(|c| c.name == "sum"));
assert!(inputs.carriers.iter().any(|c| c.name == "count"));
assert_eq!(
inputs.carriers.len(),
2,
"Should have 2 carriers (sum and count)"
);
eprintln!("[phase59/test] P3 loop-local conversion verified: sum and count correctly extracted as carriers");
}