feat(phi): Phase 25.1 - LoopForm v2 テスト・最小再現ケース追加

-  最小再現ケース作成
  - apps/tests/minimal_ssa_skip_ws.hako: 確実に再現する10-30行ケース
  - apps/tests/minimal_ssa_bug*.hako: 段階的簡略化版
  - apps/tests/loopform_*.hako: LoopForm v2 各ケーステスト

-  Rustテストスイート追加
  - src/tests/mir_loopform_conditional_reassign.rs: 4ケース(Case A/B/C/D)
  - src/tests/mir_loopform_complex.rs: 複雑なパターン
  - 全テストPASS確認済み

-  SSAバグ分析ドキュメント
  - docs/development/analysis/minimal_ssa_bug_analysis.md
  - エラー詳細・原因・ワークアラウンド記録

🎯 成果: SSAバグの構造を完全特定、デバッグ準備完了
This commit is contained in:
nyash-codex
2025-11-21 06:21:45 +09:00
parent a7ad456c8c
commit 6865f4acfa
16 changed files with 572 additions and 5 deletions

View File

@ -0,0 +1,26 @@
// loopform_break_and_return.hako
// 目的: header-cond + break + early-return が混在するループをスモークする
static box Main {
compute(limit) {
local i = 0
// Case A: header-cond (i < limit)
loop(i < limit) {
if i == 5 { return -1 } // 早期 returnexit PHI 非対象)
if i >= limit - 1 { break } // exit-break
i = i + 1
}
return i
}
main(args) {
// limit が小さい場合は break 経路で終了
local r1 = Main.compute(3)
print(r1)
// limit が大きい場合は i==5 で早期 return
local r2 = Main.compute(10)
print(r2)
return 0
}
}

View File

@ -0,0 +1,40 @@
// loopform_continue_break_scan.hako
// 目的: Region+next_i 型の scan ループで continue + break 混在パターンをスモークする
static box Main {
scan(s) {
local i = 0
local n = s.length()
// Case C-ish: constant-true + continue/backedge + break
loop(1 == 1) {
if i >= n { break }
// Region+next_i: この周回での「次の i」を 1 箇所に集約
local next_i = i
local ch = s.substring(i, i + 1)
if ch == " " {
next_i = i + 1
i = next_i
continue
}
if ch == "\t" {
next_i = i + 2
i = next_i
continue
}
// その他の文字で終了
break
}
return i
}
main(args) {
// 先頭に space + tab を並べたケースで scan が 3 を返すことを想定
local s = " \tX"
local r = Main.scan(s)
print(r)
return 0
}
}

View File

@ -0,0 +1,39 @@
// loopform_nested_region.hako
// 目的: 外側 Loop + 内側 Region 型 Loop のネストをスモークする
static box Main {
find_first_non_space(lines) {
local line_idx = 0
local n = lines.size()
// outer loop: 行単位
loop(line_idx < n) {
local line = lines.get(line_idx)
local col = 0
// inner loop: 行内の先頭非空白を探すRegion+next_col 風)
loop(1 == 1) {
if col >= line.length() { break }
local ch = line.substring(col, col + 1)
if ch == " " {
col = col + 1
continue
}
break
}
if col < line.length() { return line_idx }
line_idx = line_idx + 1
}
return -1
}
main(args) {
local lines = new ArrayBox()
lines.push(" ")
lines.push(" xx")
local r = Main.find_first_non_space(lines)
print(r)
return 0
}
}

View File

@ -0,0 +1,20 @@
// Minimal SSA Bug Reproduction
// Pattern: Conditional reassignment with immediate usage
// Expected: SSA violation "use of undefined value"
static box MinimalSSABug {
main(args) {
local x = "test"
// Problem pattern: reassign + immediate use in nested condition
if x.length() > 0 {
x = x.substring(0, 2) // reassign x
if x.length() > 0 && x.substring(0, 1) == "t" { // immediate use
x = x.substring(1, 2) // another reassign
print(x)
}
}
return 0
}
}

View File

@ -0,0 +1,28 @@
// Minimal SSA Bug Reproduction with Loop
// Pattern: Conditional reassignment inside loop with immediate usage
// Expected: SSA violation "use of undefined value"
static box MinimalSSABugLoop {
main(args) {
local src = "using foo\nusing bar\n"
local i = 0
local n = src.length()
loop(i < n) {
local line = src.substring(i, i + 10)
// Problem pattern: nested conditions with reassignment + immediate use
if line.length() > 0 {
line = line.substring(0, 5) // reassign line
if line.length() > 0 && line.substring(0, 1) == "u" { // immediate use
line = line.substring(1, 5) // another reassign
print(line)
}
}
i = i + 10
}
return 0
}
}

View File

@ -0,0 +1,24 @@
// minimal_ssa_skip_ws.hako — LoopForm exit-PHI regression canary
// 目的: loop(1 == 1) + break だけで exit PHI が壊れる既知事例を最小化
static box Main {
skip(s) {
local i = 0
local n = s.length()
// 本来は loop(i < n) だが、ワークアラウンドとして loop(1 == 1) にしていた経路で
// exit PHI が崩れて ValueId 未定義になる不具合を再現する。
loop(1 == 1) {
if i >= n { break }
local ch = s.substring(i, i + 1)
if ch == " " { i = i + 1 } else { break }
}
return i
}
main(args) {
local s = " abc"
local r = Main.skip(s)
print(r)
return 0
}
}

View File

@ -0,0 +1 @@
using "foo/bar.hako" as Foo

View File

@ -0,0 +1,9 @@
using std.collections
using "path/to/module.hako" as MyModule
static box Main {
main(args) {
print("Hello")
return 0
}
}

View File

@ -0,0 +1,23 @@
static box Main {
main(args) {
local x = args
if x == null {
x = new ArrayBox()
x.push("default")
if x.length() > 0 {
local status = "initialized"
if x.get(0) != null {
status = "has_value: " + x.length()
}
print("Status: " + status)
}
} else if x.length() >= 0 {
print("Length: " + ("" + x.length()))
}
if x == null {
return 1
}
print("Final length: " + ("" + x.length()))
return 0
}
}

View File

@ -0,0 +1,15 @@
static box Main {
main(args) {
local str = "foo/bar/baz"
local idx = -1
local t = 0
loop(t < str.length()) {
if str.substring(t, t+1) == "/" {
idx = t
}
t = t + 1
}
print("Last slash at: " + ("" + idx))
return 0
}
}

View File

@ -0,0 +1,29 @@
static box Main {
main(args) {
local src = "using foo\nusing bar"
local n = src.length()
local i = 0
loop(i < n) {
local j = i
loop(j < n && src.substring(j, j+1) != "\n") { j = j + 1 }
local line = src.substring(i, j)
if line.length() > 0 {
local p = line
local idx = -1
local t = 0
loop(t < p.length()) {
if p.substring(t,t+1) == " " { idx = t }
t = t + 1
}
if idx >= 0 {
p = p.substring(0, idx)
}
print("Processed: " + p)
}
i = j + 1
}
return 0
}
}

View File

@ -0,0 +1,28 @@
static box TestBox {
get_value() {
return "test_result"
}
}
static box Main {
main(args) {
local prog = TestBox.get_value()
{
local status = "null"
if prog != null { status = "non-null" }
print("Debug: " + status)
}
if prog == null {
print("Error: prog is null")
return null
}
local ps = "" + prog
print("Stringified length: " + ("" + ps.length()))
if ps.indexOf("test") < 0 || ps.indexOf("result") < 0 {
print("Error: unexpected output")
return null
}
print("Success")
return ps
}
}