Files
hakorune/docs/development/current/main/phase70c-merge-relay.md
nyash-codex 24cc948f61 feat(joinir): Phase 70-C - Merge Relay Detection (dev-only)
Phase 70-C enables detection of merge relay patterns where multiple inner
loops update the same owner-owned variable. This allows compile-time
validation that relay sources can be merged at owner scope's exit PHI.

Changes:
- phase70c-merge-relay.md: New SSOT doc for merge relay semantics
- normalized_joinir_min.rs: +2 integration tests

Tests: normalized_dev 54/54 PASS, lib 950/950 PASS
Design: Validator accepts multiple relays to same owner (individually valid)

🤖 Generated with Claude Code

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2025-12-13 03:41:26 +09:00

5.7 KiB

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

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:

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:

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:

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

$ 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

  • Merge relay pattern fixture created (2 inner loops → 1 owner)
  • AST analyzer correctly detects merge relay (2 relay plans)
  • OwnershipPlanValidator accepts merge relay (no runtime_unsupported error)
  • Tests pass with normalized_dev feature
  • 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)


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)