# Phase 129: Materialize join_k Continuation + LLVM Parity

## Goal

- Materialize join_k as a **real JoinFunction** (not just a concept)
- Achieve VM+LLVM EXE parity for Phase 128 if-only partial assign pattern
- Verify structural properties: join_k exists, PHI禁止 (no PHI in Normalized)

## Background

Phase 128 added basic Assign(int literal) support to Normalized builder, but:
- join_k was mentioned in comments but not actually materialized
- The If lowering doesn't generate a join continuation
- post-if statements (e.g., `return x` after if) aren't supported in Normalized path

Phase 129 fixes this by actually generating join_k as a JoinFunction.

## Design

### join_k Continuation Pattern

```
if cond {
  x = 2  // then: env_then = env with x=2
} else {
  // else: env_else = env (unchanged)
}
print(x)  // post-if: needs env from both branches
```

**Normalized Lowering**:
```
then:
  x = Const(2)
  env_then[x] = new_vid
  TailCall(join_k, env_then)

else:
  // no assignment
  env_else = env (original)
  TailCall(join_k, env_else)

join_k(env_phi):
  // env_phi is the merged environment
  // post-if statements use env_phi
  print(env_phi[x])
  Ret
```

### Key Properties (SSOT)

1. **join_k is a JoinFunction**: Not a concept, but actual function in JoinModule
2. **then/else end with TailCall(join_k)**: Not Ret, not direct post-if
3. **env merging is explicit**: env_phi parameter receives merged environment
4. **PHI禁止**: No PHI instructions in Normalized (env update + continuation only)

### Structural Verification

`verify_normalized_structure()` enforces:
- If node exists → join_k exists (as JoinFunction)
- then/else don't end with Ret (they tail-call join_k)
- join_k has env parameter (for merged environment)
- No PHI instructions (structural check)

## Scope

### In-Scope (Phase 129)

- If-only patterns (no loops)
- Assign(int literal) RHS only (Phase 128 baseline)
- post-if statements: print/return (minimal set)
- VM + LLVM EXE parity (both must work)

### Out-of-Scope

- Loop patterns (Phase 130+)
- Assign(complex expr) RHS (Phase 130+)
- Nested if (Phase 130+)

## Implementation Plan

### P0: LLVM EXE parity baseline (Phase 128) ✅

- Add `phase128_if_only_partial_assign_normalized_llvm_exe.sh`
- Verify VM+LLVM parity for Phase 128 baseline
- Regression: phase103, phase118

**Status**: DONE (commit e7ad3d31b)

### P1-B: Materialize join_k (if-as-last) ✅

**Target**:
- `src/mir/control_tree/normalized_shadow/if_as_last_join_k.rs`
- orchestrator: `src/mir/control_tree/normalized_shadow/builder.rs`
- structure verification: `src/mir/control_tree/normalized_shadow/normalized_verifier.rs`
- contract parity: `src/mir/control_tree/normalized_shadow/parity_contract.rs`

**What is supported**:
- if-only, and the `if` is the last statement (Phase 129-B)
- then/else: TailCall(join_k) with env argument (PHI禁止)

**Status**: DONE (Phase 129-B: fixture + VM smoke PASS)

### P2 (Phase 129-C): Post-if support (return-var) ✅

**New fixture**: `apps/tests/phase129_if_only_post_if_return_var_min.hako`
```hako
x=1; flag=1; if flag==1 { x=2 }; print(x); return "OK"
```

**Expected**: `2` (x updated in then branch)

**VM smoke**: `phase129_if_only_post_if_return_var_vm.sh`
- `NYASH_JOINIR_DEV=1 HAKO_JOINIR_STRICT=1`
- Verify join_k → post_k continuation works

**Implementation**:
- `src/mir/control_tree/normalized_shadow/post_if_post_k.rs` (new, 392 lines)
- Post-if lowering with post_k continuation
- join_k merges env from then/else → TailCall(post_k, merged_env)
- post_k executes post-if statements → Ret

**Status**: DONE (Phase 129-C complete)
- Fixture + VM smoke PASS
- Runs through Normalized post_k path for `return x` pattern
- Structure verification enforces join_k → post_k → Ret
- **Note**: Current fixture `phase129_if_only_post_if_return_var_min.hako` has `print(x); return "OK"` which falls back to legacy (print not in Phase 129-C scope). Simplified test with `return x` confirmed to use post_k path

### P3: Documentation

- This README (DONE)
- Update `10-Now.md` / `01-JoinIR-Selfhost-INDEX.md` / `30-Backlog.md`

## Acceptance Criteria

- ✅ P0: Phase 128 LLVM EXE smoke passes
- ✅ P1-B: join_k materialized for if-as-last (then/else tail-call join_k)
- ✅ P1-B: verify_normalized_structure enforces join_k tailcall + PHI禁止
- ✅ P2 (setup): fixture + VM smoke exist
- ✅ P2 (behavior): post-if path runs via Normalized (no fallback)
- ✅ P2 (Phase 129-C): post_k continuation implemented and verified
- ✅ P3: Documentation updated
- ✅ Regression: phase128, phase129-B all PASS
- ✅ `cargo test --lib` PASS (1155 tests)

## Verification Commands

```bash
# Unit tests
cargo test --lib

# Smoke tests (baseline)
bash tools/smokes/v2/profiles/integration/apps/phase128_if_only_partial_assign_normalized_vm.sh
bash tools/smokes/v2/profiles/integration/apps/phase128_if_only_partial_assign_normalized_llvm_exe.sh
bash tools/smokes/v2/profiles/integration/apps/phase129_join_k_as_last_vm.sh

# Regression
bash tools/smokes/v2/profiles/integration/apps/phase103_if_only_llvm_exe.sh
bash tools/smokes/v2/profiles/integration/apps/phase118_loop_nested_if_merge_vm.sh
```

## Feedback Points

### Box-First Modularization

- `lower_if_node`: Currently ~70 lines, could be split into:
  - `generate_join_k_function` (create JoinFunction)
  - `lower_if_branches` (then/else tail calls)
  - Single responsibility principle

### Fail-Fast Opportunities

- verify_normalized_structure should fail early if:
  - If exists but no join_k found
  - then/else don't tail-call join_k
  - join_k has no env parameter

### Legacy Findings

- Current `verify_branch_is_return_literal`: Too restrictive for Phase 129
  - Should allow Assign statements in branches (Phase 128 already supports)
  - Should verify tail-call to join_k (not just Return)

## Related Phases

- Phase 128: If-only partial assign (baseline)
- Phase 113: If-only partial assign parity (StepTree path)
- Phase 121-127: StepTree→Normalized foundation
- Phase 130+: Loop patterns, complex RHS

## Notes

- **Dev-only**: `joinir_dev_enabled()` + `HAKO_JOINIR_STRICT=1` required
- **PHI禁止**: Normalized path doesn't use PHI (env update + continuation instead)
- **join_k naming**: Could use `join_k`, `k_join`, or auto-generate unique ID
