test(joinir): Phase 129 P2 - add post-if return var fixture + VM smoke
- Add phase129_if_only_post_if_return_var_min.hako
- Pattern: x=1; if flag==1 { x=2 }; print(x)
- Tests join_k continuation env merge
- Add phase129_if_only_post_if_return_var_vm.sh
- Expected output: 2 (x updated in then branch)
- Dev-only: NYASH_JOINIR_DEV=1 HAKO_JOINIR_STRICT=1
Note: Currently passes via fallback path (non-Normalized)
P1 implementation (join_k materialization) is next step
This commit is contained in:
22
apps/tests/phase129_if_only_post_if_return_var_min.hako
Normal file
22
apps/tests/phase129_if_only_post_if_return_var_min.hako
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
193
docs/development/current/main/phases/phase-129/README.md
Normal file
193
docs/development/current/main/phases/phase-129/README.md
Normal file
@ -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
|
||||||
@ -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
|
||||||
Reference in New Issue
Block a user