Files
hakorune/docs/development/current/main/phases/phase-131/p1.5-implementation-guide.md
nyash-codex bfac188732 docs: Phase 131 P1.5 DirectValue exit reconnection design
Add design documents for Phase 131 P1.5 DirectValue mode:
- Root cause analysis of PHI-based exit merge assumptions
- Option B (DirectValue) analysis and trade-offs
- Implementation guide for exit value reconnection

Also add exit_reconnector.rs module stub for future extraction.

Related:
- Phase 131: loop(true) break-once Normalized support
- Normalized shadow path uses continuations, not PHI
- Exit values reconnect directly to host variable_map

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-18 17:47:45 +09:00

9.3 KiB

Phase 131 P1.5: Implementation Guide (Quick Reference)

Status: Implementation Guide Scope: Step-by-step implementation instructions for Option B Related:

  • Root Cause: p1.5-root-cause-summary.md
  • Design: p1.5-option-b-analysis.md

Implementation Sequence

Step 1: Create ExitReconnectorBox (New File)

File: src/mir/control_tree/normalized_shadow/exit_reconnector.rs

Responsibility: Generate host variable bindings from k_exit env parameters (Normalized IR only, PHI-free)

API:

pub struct ExitReconnectorBox;

impl ExitReconnectorBox {
    /// Reconnect k_exit env parameters to host variable_map
    ///
    /// For Normalized IR only: k_exit env params are SSOT for final values
    pub fn reconnect(
        builder: &mut MirBuilder,
        exit_meta: &ExitMeta,
        env_layout: &EnvLayout,
        debug: bool,
    ) -> Result<(), String> {
        // For each exit_value (var_name, k_exit_param_vid)
        // - k_exit_param_vid is already the final value (SSOT)
        // - Update builder.variable_ctx.variable_map[var_name] = k_exit_param_vid
        //
        // Strict mode: verify all env_layout.writes are in exit_meta
        //
        // Returns: Ok(()) on success, Err(msg) on contract violation
    }
}

Algorithm:

// 1. Validate (strict mode only)
if strict_enabled() {
    for var in &env_layout.writes {
        if !exit_meta.exit_values.iter().any(|(name, _)| name == var) {
            return Err(freeze_with_hint(
                "phase131/exit_reconnect/missing",
                &format!("Variable '{}' in writes but not in k_exit env", var),
                "Ensure env_layout.writes matches k_exit parameter list (SSOT)"
            ));
        }
    }
}

// 2. Update variable_map directly (no PHI)
for (var_name, k_exit_param_vid) in &exit_meta.exit_values {
    // k_exit_param_vid is the final value (SSOT)
    builder.variable_ctx.variable_map.insert(
        var_name.clone(),
        *k_exit_param_vid
    );

    if debug {
        eprintln!(
            "[phase131/exit_reconnect] {}{:?}",
            var_name, k_exit_param_vid
        );
    }
}

File Template:

//! Phase 131 P1.5: ExitReconnectorBox - Normalized Exit Reconnection
//!
//! ## Responsibility
//!
//! - Wire k_exit env parameters to host variable_map (Normalized IR only)
//! - PHI-free: k_exit env params are SSOT for final values
//! - Fail-Fast: Strict mode catches contract violations
//!
//! ## Contract
//!
//! - Only used for Normalized shadow path
//! - k_exit env parameters represent final variable values (SSOT)
//! - No PHI generation (direct variable_map update)
//!
//! ## Design Philosophy
//!
//! Normalized IR uses continuation-based control flow:
//! - main(env) → loop_step(env) → loop_body(env) → k_exit(env)
//! - k_exit receives final env state as parameters
//! - No loop back edges, no PHI merging needed
//! - k_exit env params ARE the truth (SSOT)

use crate::mir::builder::MirBuilder;
use crate::mir::control_tree::normalized_shadow::env_layout::EnvLayout;
use crate::mir::join_ir::lowering::carrier_info::ExitMeta;
use crate::mir::join_ir::lowering::error_tags;

pub struct ExitReconnectorBox;

impl ExitReconnectorBox {
    // ... implementation here ...
}

#[cfg(test)]
mod tests {
    // ... tests here ...
}

Step 2: Update routing.rs (Bypass Merge Pipeline)

File: src/mir/builder/control_flow/joinir/routing.rs

Location: Lines 441-532 (current Normalized shadow handling)

Changes:

  1. Add env_layout to JoinFragmentMeta (if not already there)
  2. Detect Normalized shadow path
  3. Use ExitReconnectorBox instead of merge pipeline

Code Change:

// Around line 441
match StepTreeNormalizedShadowLowererBox::try_lower_if_only(&tree, &available_inputs) {
    Ok(Some((join_module, join_meta))) => {
        // ... existing trace ...

        // Phase 131 P1.5: Normalized-specific exit reconnection
        // Bypass PHI-based merge pipeline, use direct env→host wiring
        use crate::mir::control_tree::normalized_shadow::exit_reconnector::ExitReconnectorBox;
        use crate::mir::control_tree::normalized_shadow::env_layout::EnvLayout;

        // Build env_layout from contract (or get from join_meta if available)
        let env_layout = EnvLayout::from_contract(&tree.contract, &available_inputs);

        // Direct reconnection (no PHI)
        ExitReconnectorBox::reconnect(
            self,
            &join_meta.exit_meta,
            &env_layout,
            debug,
        )?;

        trace::trace().routing(
            "router/normalized",
            func_name,
            "Normalized exit reconnection complete (PHI-free)",
        );

        // Return void constant (loop executed successfully)
        // Note: If k_exit returns a value, use that instead
        use crate::mir::{ConstValue, MirInstruction};
        let void_id = self.next_value_id();
        self.emit_instruction(MirInstruction::Const {
            dst: void_id,
            value: ConstValue::Void,
        })?;

        Ok(Some(void_id))
    }
    Ok(None) => {
        // ... existing fallback ...
    }
    Err(e) => {
        // ... existing error handling ...
    }
}

Note: Remove the existing ExitMetaCollector::collect() and JoinIRConversionPipeline::execute() calls - those are for standard loops with PHI.

Step 3: Update mod.rs (Export ExitReconnectorBox)

File: src/mir/control_tree/normalized_shadow/mod.rs

Add:

pub mod exit_reconnector;
pub use exit_reconnector::ExitReconnectorBox;

Step 4: Testing

Unit Tests (in exit_reconnector.rs)

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_empty_writes() {
        // When env_layout.writes is empty
        // Should return Ok(()) without updating variable_map
    }

    #[test]
    fn test_single_write() {
        // When env_layout.writes = ["x"]
        // And exit_meta.exit_values = [("x", ValueId(6))]
        // Should update variable_map["x"] = ValueId(6)
    }

    #[test]
    #[should_panic]
    fn test_missing_variable_strict() {
        // When strict mode enabled
        // And env_layout.writes = ["x", "y"]
        // But exit_meta.exit_values = [("x", ValueId(6))]
        // Should panic with freeze_with_hint
    }
}

Integration Tests

  1. VM Smoke:

    bash tools/smokes/v2/profiles/integration/apps/phase131_loop_true_break_once_vm.sh
    

    Expected: [PASS] Output verified: 1 (exit code: 1)

  2. LLVM EXE Smoke:

    bash tools/smokes/v2/profiles/integration/apps/phase131_loop_true_break_once_llvm_exe.sh
    

    Expected: [PASS] Output verified: 1

  3. Regression:

    # If-only patterns (Phase 130)
    bash tools/smokes/v2/profiles/integration/apps/phase130_if_only_post_if_add_vm.sh
    
    # Existing JoinIR patterns (Phase 97)
    bash tools/smokes/v2/profiles/integration/apps/phase97_next_non_ws_llvm_exe.sh
    

    Expected: All PASS (既定挙動不変)

Step 5: Verification Checklist

Before committing:

  • cargo build --release → 0 errors
  • cargo test --lib → all tests pass
  • Phase 131 VM smoke → PASS
  • Phase 131 LLVM EXE smoke → PASS
  • Phase 130 regression → PASS
  • Phase 97 regression → PASS

Commit Sequence

Commit 1: Create ExitReconnectorBox

feat(normalized): Phase 131 P1.5 ExitReconnectorBox for host variable propagation

- Add exit_reconnector.rs with pure function design
- Wire k_exit env params to host variable_map (PHI-free)
- Strict mode verifier for contract violations
- Unit tests for empty/single/multiple writes

Commit 2: Update Routing

fix(joinir/dev): bypass PHI generation for Normalized path

- Detect Normalized shadow in routing.rs
- Use ExitReconnectorBox instead of merge pipeline
- Direct env→host wiring (no PHI generation)
- Existing patterns (Pattern 1-4) unaffected

Commit 3: Enable Tests

test(joinir): Phase 131 P1.5 enforce return parity (VM + LLVM EXE)

- Enable phase131 VM/LLVM EXE smokes
- Verify regression tests (phase130, phase97)
- Update test expectations

Commit 4: Documentation

docs: Phase 131 P1.5 DONE (Normalized exit reconnection via Option B)

- Update 10-Now.md with completion status
- Update phase-131/README.md with P1.5 summary
- Add root cause analysis docs

Common Pitfalls to Avoid

  1. Don't touch merge pipeline: Standard loops (Pattern 1-4) still use it
  2. Don't generate PHI: Normalized IR is PHI-free by design
  3. Don't forget strict mode: Contract violations should freeze
  4. Don't skip regression tests: 既定挙動不変 is critical

Key Principles (SSOT)

  1. PHI禁止維持: Normalized IR never generates PHI nodes
  2. 既定挙動不変: Standard patterns use existing merge pipeline
  3. 責務分離: ExitReconnectorBox handles Normalized-specific wiring
  4. 箱化モジュール化: New logic in dedicated box
  5. Fail-Fast: Strict mode catches violations immediately

Success Criteria

  • loop(true) { x=1; break }; return x → returns 1 (not 0)
  • VM and LLVM EXE both produce same result
  • No regression in existing patterns
  • Clean trace output (no PHI errors)
  • Strict mode catches contract violations

Reference

  • Root Cause: p1.5-root-cause-summary.md
  • Design Rationale: p1.5-option-b-analysis.md
  • Phase 131 Overview: README.md