# 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: 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 continuation works **Status**: - Fixture + VM smoke exist. - Not yet guaranteed to run through Normalized join_k path (can still fall back). ### 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) - [ ] P3: Documentation updated - [ ] Regression: phase103, phase118, phase128 all PASS - [ ] `cargo test --lib` PASS ## 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