diff --git a/apps/tests/phase115_if_only_call_merge_min.hako b/apps/tests/phase115_if_only_call_merge_min.hako new file mode 100644 index 00000000..062d2b84 --- /dev/null +++ b/apps/tests/phase115_if_only_call_merge_min.hako @@ -0,0 +1,25 @@ +// Phase 115: if-only call result merge parity +// Test if-branch call result merge (LLVM fragile pattern) + +static box Main { + f(x) { + return x + 1 + } + + g(flag) { + local v + v = 0 + if flag == 1 { + v = me.f(1) + } else { + v = me.f(2) + } + print(v) + } + + main() { + me.g(1) + me.g(0) + return "OK" + } +} diff --git a/docs/development/current/main/01-JoinIR-Selfhost-INDEX.md b/docs/development/current/main/01-JoinIR-Selfhost-INDEX.md index 4dccedce..f149c9da 100644 --- a/docs/development/current/main/01-JoinIR-Selfhost-INDEX.md +++ b/docs/development/current/main/01-JoinIR-Selfhost-INDEX.md @@ -66,17 +66,19 @@ JoinIR の箱構造と責務、ループ/if の lowering パターンを把握 - `docs/development/current/main/phases/phase-113/README.md` 16. Phase 114: if-only return+post parity(early return + post-if statements) - `docs/development/current/main/phases/phase-114/README.md` -17. Phase 104: loop(true) break-only digits(VM + LLVM EXE) +17. Phase 115: if-only call result merge parity(関数呼び出し結果 merge) + - `docs/development/current/main/phases/phase-115/README.md` +18. Phase 104: loop(true) break-only digits(VM + LLVM EXE) - `docs/development/current/main/phases/phase-104/README.md` -18. Phase 107: json_cur find_balanced_* depth scan(VM + LLVM EXE) +19. Phase 107: json_cur find_balanced_* depth scan(VM + LLVM EXE) - `docs/development/current/main/phases/phase-107/README.md` -19. Phase 108: Pattern2 policy router SSOT(入口の薄さを固定) +20. Phase 108: Pattern2 policy router SSOT(入口の薄さを固定) - `docs/development/current/main/phases/phase-108/README.md` -20. Phase 109: error_tags hints SSOT(Fail-Fast + hint の語彙固定) +21. Phase 109: error_tags hints SSOT(Fail-Fast + hint の語彙固定) - `docs/development/current/main/phases/phase-109/README.md` -21. MIR Builder(Context 分割の入口) +22. MIR Builder(Context 分割の入口) - `src/mir/builder/README.md` -22. Scope/BindingId(shadowing・束縛同一性の段階移行) +23. Scope/BindingId(shadowing・束縛同一性の段階移行) - `docs/development/current/main/phase73-scope-manager-design.md` - `docs/development/current/main/PHASE_74_SUMMARY.md` - `docs/development/current/main/PHASE_75_SUMMARY.md` diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index 50607571..a041dc6a 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -1,5 +1,14 @@ # Self Current Task — Now (main) +## 2025-12-18:Phase 115 完了 ✅ + +**Phase 115: if-only call result merge parity** +- if-only で関数呼び出し結果を merge するパターンを VM/LLVM で固定 +- Fixture: phase115_if_only_call_merge_min.hako (expected: 2, 3) +- Smoke: VM + LLVM EXE parity 検証済み +- 回帰: Phase 103/113/114 維持確認 +- 入口: `docs/development/current/main/phases/phase-115/README.md` + ## 2025-12-18:Phase 114 完了 ✅ **Phase 114: if-only return+post parity** diff --git a/docs/development/current/main/phases/phase-115/README.md b/docs/development/current/main/phases/phase-115/README.md new file mode 100644 index 00000000..9035444c --- /dev/null +++ b/docs/development/current/main/phases/phase-115/README.md @@ -0,0 +1,91 @@ +# Phase 115: If-Only Call Result Merge Parity + +**Status**: ✅ DONE +**Date**: 2025-12-18 + +## 背景 + +LLVMバックエンドで壊れやすいパターン - if分岐内での関数呼び出し結果をマージするケース。 + +### 問題パターン + +```hako +fn f(x) { return x + 1 } +fn g(flag) { + local v = 0 + if flag == 1 { v = f(1) } else { v = f(2) } + print(v) +} +``` + +このパターンでは: +1. if/else両分岐で関数呼び出し `f()` が発生 +2. その結果を変数 `v` に代入 +3. if後にマージされた `v` を使用 + +LLVM EXEモードでは、PHI node生成やSSA変換が正しく行われない可能性がある。 + +## 実装内容 + +### 1. テストフィクスチャ + +- **ファイル**: `/home/tomoaki/git/hakorune-selfhost/apps/tests/phase115_if_only_call_merge_min.hako` +- **期待出力**: `2\n3` + - `g(1)` → `f(1)` → `1+1` → `2` + - `g(0)` → `f(2)` → `2+1` → `3` + +### 2. VM Smoke Test + +- **ファイル**: `/home/tomoaki/git/hakorune-selfhost/tools/smokes/v2/profiles/integration/apps/phase115_if_only_call_merge_vm.sh` +- **環境変数**: `NYASH_DISABLE_PLUGINS=1 HAKO_JOINIR_STRICT=1` +- **検証**: 数値行抽出して `2\n3` と比較 + +### 3. LLVM EXE Smoke Test + +- **ファイル**: `/home/tomoaki/git/hakorune-selfhost/tools/smokes/v2/profiles/integration/apps/phase115_if_only_call_merge_llvm_exe.sh` +- **利用**: `tools/smokes/v2/lib/llvm_exe_runner.sh` +- **検証**: `EXPECTED='2\n3'`, `EXPECTED_LINES=2` + +## 検証コマンド + +```bash +# VM smoke test +bash tools/smokes/v2/profiles/integration/apps/phase115_if_only_call_merge_vm.sh + +# LLVM EXE smoke test +bash tools/smokes/v2/profiles/integration/apps/phase115_if_only_call_merge_llvm_exe.sh + +# Phase 114回帰テスト(推奨) +bash tools/smokes/v2/profiles/integration/apps/phase114_if_only_return_then_post_llvm_exe.sh +``` + +## 関連Phase + +- **Phase 103**: If-Only基本パリティ(制御フロー基礎) +- **Phase 113**: If-Only部分代入パリティ(変数マージ) +- **Phase 114**: If-Only return+post パリティ(early returnとpost-if文) +- **Phase 115**: If-Only call result merge パリティ(関数呼び出し結果マージ) ← 今回 + +## 技術的詳細 + +### JoinIR → MIR変換 + +If-Only Pattern 1では: +1. Then/Else分岐それぞれで関数呼び出し +2. 各分岐の終端で変数への代入 +3. Exit block入口でPHI node生成(then_value vs else_value) +4. Post-if文でマージされた値を使用 + +### LLVM SSA変換 + +LLVM EXEモードでは: +1. 関数呼び出し結果を一時レジスタに保持 +2. 各分岐終端でstore +3. Exit block入口でload + phi +4. Post-if文でphi結果を使用 + +## Fail-Fast原則 + +- プラグイン無効化(`NYASH_DISABLE_PLUGINS=1`)でコア機能のみテスト +- JoinIR厳格モード(`HAKO_JOINIR_STRICT=1`)で不正な制御フローを即座に検出 +- 数値行のみ抽出して検証(余計なログを排除) diff --git a/tools/smokes/v2/profiles/integration/apps/phase115_if_only_call_merge_llvm_exe.sh b/tools/smokes/v2/profiles/integration/apps/phase115_if_only_call_merge_llvm_exe.sh new file mode 100644 index 00000000..a68b05c5 --- /dev/null +++ b/tools/smokes/v2/profiles/integration/apps/phase115_if_only_call_merge_llvm_exe.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# Phase 115: if-only call result merge parity (LLVM EXE) + +source "$(dirname "$0")/../../../lib/test_runner.sh" +source "$(dirname "$0")/../../../lib/llvm_exe_runner.sh" +export SMOKES_USE_PYVM=0 +require_env || exit 2 + +llvm_exe_preflight_or_skip || exit 0 + +# Phase 97/98/100 SSOT: plugin dlopen check → build only if needed → dlopen recheck. +FILEBOX_SO="$NYASH_ROOT/plugins/nyash-filebox-plugin/libnyash_filebox_plugin.so" +MAPBOX_SO="$NYASH_ROOT/plugins/nyash-map-plugin/libnyash_map_plugin.so" +STRINGBOX_SO="$NYASH_ROOT/plugins/nyash-string-plugin/libnyash_string_plugin.so" +CONSOLEBOX_SO="$NYASH_ROOT/plugins/nyash-console-plugin/libnyash_console_plugin.so" +INTEGERBOX_SO="$NYASH_ROOT/plugins/nyash-integer-plugin/libnyash_integer_plugin.so" + +LLVM_REQUIRED_PLUGINS=( + "FileBox|$FILEBOX_SO|nyash-filebox-plugin" + "MapBox|$MAPBOX_SO|nyash-map-plugin" + "StringBox|$STRINGBOX_SO|nyash-string-plugin" + "ConsoleBox|$CONSOLEBOX_SO|nyash-console-plugin" + "IntegerBox|$INTEGERBOX_SO|nyash-integer-plugin" +) +LLVM_PLUGIN_BUILD_LOG="/tmp/phase115_if_only_call_merge_plugin_build.log" +llvm_exe_ensure_plugins_or_fail || exit 1 + +INPUT_HAKO="$NYASH_ROOT/apps/tests/phase115_if_only_call_merge_min.hako" +OUTPUT_EXE="$NYASH_ROOT/tmp/phase115_if_only_call_merge_llvm_exe" + +EXPECTED=$'2\n3' +EXPECTED_LINES=2 +LLVM_BUILD_LOG="/tmp/phase115_if_only_call_merge_build.log" +if llvm_exe_build_and_run_numeric_smoke; then + test_pass "phase115_if_only_call_merge_llvm_exe: output matches expected (2\\n3)" +else + exit 1 +fi diff --git a/tools/smokes/v2/profiles/integration/apps/phase115_if_only_call_merge_vm.sh b/tools/smokes/v2/profiles/integration/apps/phase115_if_only_call_merge_vm.sh new file mode 100644 index 00000000..1419fcbe --- /dev/null +++ b/tools/smokes/v2/profiles/integration/apps/phase115_if_only_call_merge_vm.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# Phase 115: if-only call result merge parity (VM) +# +# Verifies that if-only with call result merge works correctly. + +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} + +INPUT="$NYASH_ROOT/apps/tests/phase115_if_only_call_merge_min.hako" + +echo "[INFO] Phase 115: if-only call result merge parity (VM) - $INPUT" + +set +e +OUTPUT=$(timeout "$RUN_TIMEOUT_SECS" env \ + NYASH_DISABLE_PLUGINS=1 \ + HAKO_JOINIR_STRICT=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 + EXPECTED=$'2\n3' + if validate_numeric_output 2 "$EXPECTED" "$OUTPUT"; then + echo "[PASS] Output verified: 2\\n3" + 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 "phase115_if_only_call_merge_vm: All tests passed" + exit 0 +else + test_fail "phase115_if_only_call_merge_vm: $FAIL_COUNT test(s) failed" + exit 1 +fi