diff --git a/apps/tests/phase129_if_only_post_if_return_var_min.hako b/apps/tests/phase129_if_only_post_if_return_var_min.hako new file mode 100644 index 00000000..735392af --- /dev/null +++ b/apps/tests/phase129_if_only_post_if_return_var_min.hako @@ -0,0 +1,22 @@ +// Phase 129: if-only post-if return var (Normalized join_k continuation) +// Pattern: x=1; if flag==1 { x=2 }; return x +// Expected: x=2 (flag=1 fixed) +// +// This tests that join_k continuation properly merges env: +// - then: x=2 (updated) +// - else: x=1 (original) +// - join_k: receives env_phi and continues to return x + +static box Main { + main() { + local x + local flag + x = 1 + flag = 1 + if flag == 1 { + x = 2 + } + print(x) + return "OK" + } +} diff --git a/docs/development/current/main/phases/phase-129/README.md b/docs/development/current/main/phases/phase-129/README.md new file mode 100644 index 00000000..1620eb5b --- /dev/null +++ b/docs/development/current/main/phases/phase-129/README.md @@ -0,0 +1,193 @@ +# 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 smoke for 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: Materialize join_k Continuation + +**Target**: `src/mir/control_tree/normalized_shadow/builder.rs` + +**Changes**: +1. **lower_if_node**: Generate join_k function + - Create JoinFunction with env parameter + - then: `TailCall(join_k, env_then)` + - else: `TailCall(join_k, env_else)` + - join_k body: process post-if statements + +2. **verify_normalized_structure**: Add join_k checks + - If exists → join_k exists (by name or structure) + - then/else end with TailCall (not Ret) + - join_k has params (env fields) + - No PHI instructions anywhere + +**Acceptance**: +- `cargo test --lib` PASS +- Phase 128 VM smoke PASS +- Phase 129 LLVM EXE smoke PASS (new fixture) + +### P2: Post-If Return Var Fixture + +**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 + +**Acceptance**: +- VM smoke PASS +- join_k actually used (not fallback 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: join_k materialized in builder.rs +- [ ] P1: verify_normalized_structure enforces join_k properties +- [ ] P2: Phase 129 fixture + VM smoke passes +- [ ] P3: Documentation updated +- [ ] Regression: phase103, phase118, phase128 all PASS +- [ ] `cargo test --lib` PASS + +## Verification Commands + +```bash +# Unit tests +cargo test --lib + +# Smoke tests +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_if_only_post_if_return_var_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 diff --git a/tools/smokes/v2/profiles/integration/apps/phase129_if_only_post_if_return_var_vm.sh b/tools/smokes/v2/profiles/integration/apps/phase129_if_only_post_if_return_var_vm.sh new file mode 100644 index 00000000..1fe4575f --- /dev/null +++ b/tools/smokes/v2/profiles/integration/apps/phase129_if_only_post_if_return_var_vm.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# Phase 129: If-only Post-If Return Var (Normalized join_k, VM) +# +# Verifies that join_k continuation properly merges env after if: +# - x=1; flag=1; if flag==1 { x=2 }; return x +# - join_k receives env_phi (x=2 from then, x=1 from else) +# - Returns x=2 (flag=1 fixed) +# - Dev-only: NYASH_JOINIR_DEV=1 HAKO_JOINIR_STRICT=1 + +source "$(dirname "$0")/../../../lib/test_runner.sh" +source "$(dirname "$0")/../../../lib/output_validator.sh" +export SMOKES_USE_PYVM=0 +require_env || exit 2 + +PASS_COUNT=0 +FAIL_COUNT=0 +RUN_TIMEOUT_SECS=${RUN_TIMEOUT_SECS:-10} + +echo "[INFO] Phase 129: If-only Post-If Return Var (Normalized join_k, VM)" + +# Test 1: phase129_if_only_post_if_return_var_min.hako +echo "[INFO] Test 1: phase129_if_only_post_if_return_var_min.hako" +INPUT="$NYASH_ROOT/apps/tests/phase129_if_only_post_if_return_var_min.hako" + +set +e +OUTPUT=$(timeout "$RUN_TIMEOUT_SECS" env \ + NYASH_DISABLE_PLUGINS=1 \ + HAKO_JOINIR_STRICT=1 \ + NYASH_JOINIR_DEV=1 \ + "$NYASH_BIN" --backend vm "$INPUT" 2>&1) +EXIT_CODE=$? +set -e + +if [ "$EXIT_CODE" -eq 124 ]; then + echo "[FAIL] hakorune timed out (>${RUN_TIMEOUT_SECS}s)" + FAIL_COUNT=$((FAIL_COUNT + 1)) +elif [ "$EXIT_CODE" -eq 0 ]; then + # Phase 129: expect x=2 (print(x) after if with x=2) + EXPECTED="2" + if validate_numeric_output 1 "$EXPECTED" "$OUTPUT"; then + echo "[PASS] Output verified: 2 (exit code: $EXIT_CODE)" + PASS_COUNT=$((PASS_COUNT + 1)) + else + echo "[INFO] output (tail):" + echo "$OUTPUT" | tail -n 50 || true + FAIL_COUNT=$((FAIL_COUNT + 1)) + fi +else + echo "[FAIL] hakorune failed with exit code $EXIT_CODE" + echo "[INFO] output (tail):" + echo "$OUTPUT" | tail -n 50 || true + FAIL_COUNT=$((FAIL_COUNT + 1)) +fi + +echo "[INFO] PASS: $PASS_COUNT, FAIL: $FAIL_COUNT" + +if [ "$FAIL_COUNT" -eq 0 ]; then + test_pass "phase129_if_only_post_if_return_var_vm: All tests passed" + exit 0 +else + test_fail "phase129_if_only_post_if_return_var_vm: $FAIL_COUNT test(s) failed" + exit 1 +fi