209 lines
5.7 KiB
Markdown
209 lines
5.7 KiB
Markdown
|
|
# Phase 70-C: Merge Relay (Multiple Inner Loops → Same Owner)
|
||
|
|
|
||
|
|
**Status**: ✅ Completed
|
||
|
|
**Date**: 2025-12-13
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
Phase 70-C implements detection and validation support for "merge relay" patterns where **multiple inner loops update the same owner-owned variable**.
|
||
|
|
|
||
|
|
This builds upon Phase 70-B's simple passthrough multihop support by handling the case where a single owner scope must merge updates from multiple relay sources.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Merge Relay Pattern
|
||
|
|
|
||
|
|
### Definition
|
||
|
|
|
||
|
|
**Merge Relay**: Multiple inner loops update the same ancestor-owned variable, requiring the owner to merge all relay updates at its exit PHI.
|
||
|
|
|
||
|
|
### Example
|
||
|
|
|
||
|
|
```nyash
|
||
|
|
loop L1 {
|
||
|
|
local total = 0 // owned by L1
|
||
|
|
loop L2_A {
|
||
|
|
total++ // L2_A → L1 relay
|
||
|
|
}
|
||
|
|
loop L2_B {
|
||
|
|
total += 10 // L2_B → L1 relay
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// L1 exit: merge both L2_A and L2_B's updates to 'total'
|
||
|
|
```
|
||
|
|
|
||
|
|
### OwnershipPlans
|
||
|
|
|
||
|
|
**L2_A Plan**:
|
||
|
|
```rust
|
||
|
|
OwnershipPlan {
|
||
|
|
scope_id: ScopeId(2),
|
||
|
|
relay_writes: [
|
||
|
|
RelayVar {
|
||
|
|
name: "total",
|
||
|
|
owner_scope: ScopeId(1), // L1
|
||
|
|
relay_path: [ScopeId(2)], // Single hop
|
||
|
|
}
|
||
|
|
],
|
||
|
|
owned_vars: [], // L2_A doesn't own total
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**L2_B Plan**:
|
||
|
|
```rust
|
||
|
|
OwnershipPlan {
|
||
|
|
scope_id: ScopeId(3),
|
||
|
|
relay_writes: [
|
||
|
|
RelayVar {
|
||
|
|
name: "total",
|
||
|
|
owner_scope: ScopeId(1), // L1 (same owner)
|
||
|
|
relay_path: [ScopeId(3)], // Single hop
|
||
|
|
}
|
||
|
|
],
|
||
|
|
owned_vars: [], // L2_B doesn't own total
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**L1 Plan**:
|
||
|
|
```rust
|
||
|
|
OwnershipPlan {
|
||
|
|
scope_id: ScopeId(1),
|
||
|
|
owned_vars: [
|
||
|
|
ScopeOwnedVar {
|
||
|
|
name: "total",
|
||
|
|
is_written: true, // Owner accepts relays
|
||
|
|
}
|
||
|
|
],
|
||
|
|
relay_writes: [], // No further relay (L1 is owner)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Design Decisions
|
||
|
|
|
||
|
|
### 1. PERMIT with Owner Merge
|
||
|
|
|
||
|
|
**Decision**: Merge relay is **PERMITTED** (not rejected) because:
|
||
|
|
- Multiple inner loops updating a shared variable is a **legitimate pattern**
|
||
|
|
- Owner scope can merge all updates at its exit PHI
|
||
|
|
- Each inner loop has a valid single-hop relay to the same owner
|
||
|
|
|
||
|
|
### 2. Validation Strategy
|
||
|
|
|
||
|
|
**OwnershipPlanValidator accepts merge relay if**:
|
||
|
|
1. Each relay has valid `relay_path` (non-empty, first hop is current scope)
|
||
|
|
2. No self-conflict (scope doesn't both own and relay the same variable)
|
||
|
|
3. All relays point to the same owner scope (validated separately per relay)
|
||
|
|
|
||
|
|
**Note**: Validator validates **individual plans** - it doesn't check cross-plan consistency (e.g., "two different scopes relay to the same owner"). This is intentional - each relay is independently valid.
|
||
|
|
|
||
|
|
### 3. Runtime Support
|
||
|
|
|
||
|
|
**Phase 70-C Status**: Detection and validation only (dev-only)
|
||
|
|
|
||
|
|
**Runtime Implementation** (Phase 70-D+):
|
||
|
|
- Owner scope exit PHI must merge values from all relay sources
|
||
|
|
- Each relay source appears as a separate carrier in owner's loop_step
|
||
|
|
- PHI merges: `total_final = phi(total_init, total_from_L2_A, total_from_L2_B)`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Implementation
|
||
|
|
|
||
|
|
### Files Modified
|
||
|
|
|
||
|
|
1. **tests/normalized_joinir_min.rs**
|
||
|
|
- `test_phase70c_merge_relay_multiple_inner_loops_detected()`: AST → OwnershipPlan detection
|
||
|
|
- `test_phase70c_merge_relay_same_owner_accepted()`: Validator accepts merge relay
|
||
|
|
|
||
|
|
### Test Coverage
|
||
|
|
|
||
|
|
#### Test 1: AST Detection
|
||
|
|
- **Input**: 3-level nested loop (L1 owns, L2_A and L2_B relay)
|
||
|
|
- **Verification**:
|
||
|
|
- 2 relay plans detected
|
||
|
|
- Both relay to the same owner (L1)
|
||
|
|
- Single-hop relay paths
|
||
|
|
- Owner plan marks variable as written
|
||
|
|
|
||
|
|
#### Test 2: Validator Acceptance
|
||
|
|
- **Input**: Two OwnershipPlans with single-hop relay to same owner
|
||
|
|
- **Verification**:
|
||
|
|
- Both plans pass `OwnershipPlanValidator::validate_relay_support()`
|
||
|
|
- No runtime_unsupported errors
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Test Results
|
||
|
|
|
||
|
|
```bash
|
||
|
|
$ NYASH_JOINIR_NORMALIZED_DEV_RUN=1 cargo test --features normalized_dev --test normalized_joinir_min test_phase70c
|
||
|
|
|
||
|
|
running 2 tests
|
||
|
|
test test_phase70c_merge_relay_same_owner_accepted ... ok
|
||
|
|
test test_phase70c_merge_relay_multiple_inner_loops_detected ... ok
|
||
|
|
|
||
|
|
test result: ok. 2 passed; 0 failed; 0 ignored
|
||
|
|
```
|
||
|
|
|
||
|
|
**Regression Check**:
|
||
|
|
- normalized_dev: 54/54 PASS ✅
|
||
|
|
- lib tests: 950/950 PASS ✅
|
||
|
|
- Zero regressions
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Acceptance Criteria
|
||
|
|
|
||
|
|
- [x] Merge relay pattern fixture created (2 inner loops → 1 owner)
|
||
|
|
- [x] AST analyzer correctly detects merge relay (2 relay plans)
|
||
|
|
- [x] OwnershipPlanValidator accepts merge relay (no runtime_unsupported error)
|
||
|
|
- [x] Tests pass with normalized_dev feature
|
||
|
|
- [x] No regressions in lib tests
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Future Work (Phase 70-D+)
|
||
|
|
|
||
|
|
### Runtime Execution Support
|
||
|
|
|
||
|
|
**Required for full merge relay execution**:
|
||
|
|
|
||
|
|
1. **Exit PHI Generation**:
|
||
|
|
- Owner scope must generate PHI with N+1 inputs (init + each relay source)
|
||
|
|
- Example: `phi(total_init, total_from_L2_A, total_from_L2_B)`
|
||
|
|
|
||
|
|
2. **Carrier Propagation**:
|
||
|
|
- Each relay source must appear in owner's loop_step signature
|
||
|
|
- Owner boundary must collect all relay values
|
||
|
|
|
||
|
|
3. **Integration Testing**:
|
||
|
|
- E2E test: 2 inner loops → owner → verify final merged value
|
||
|
|
- Combination: Multihop + Merge (3-layer nested with multiple relays)
|
||
|
|
|
||
|
|
4. **Edge Cases**:
|
||
|
|
- More than 2 relay sources
|
||
|
|
- If-branches with relay (conditional merge)
|
||
|
|
- Multihop + Merge (nested relay with multiple sources)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Related Documents
|
||
|
|
|
||
|
|
- [Phase 65: Multihop Design](phase65-ownership-relay-multihop-design.md#merge-relay-の意味論)
|
||
|
|
- [Phase 70-A: Runtime Guard](phase70-relay-runtime-guard.md)
|
||
|
|
- [Phase 70-B: Simple Passthrough](phase70-relay-runtime-guard.md)
|
||
|
|
- [Phase 56: Ownership-Relay Architecture](phase56-ownership-relay-design.md)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Changelog
|
||
|
|
|
||
|
|
- **2025-12-13**: Phase 70-C completed - Merge relay detection and validation
|
||
|
|
- 2 tests added (AST detection, validator acceptance)
|
||
|
|
- 54/54 normalized_dev tests pass
|
||
|
|
- 950/950 lib tests pass (zero regressions)
|