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:
26
apps/tests/loopform_break_and_return.hako
Normal file
26
apps/tests/loopform_break_and_return.hako
Normal 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 } // 早期 return(exit 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
|
||||
}
|
||||
}
|
||||
|
||||
40
apps/tests/loopform_continue_break_scan.hako
Normal file
40
apps/tests/loopform_continue_break_scan.hako
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
39
apps/tests/loopform_nested_region.hako
Normal file
39
apps/tests/loopform_nested_region.hako
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
20
apps/tests/minimal_ssa_bug.hako
Normal file
20
apps/tests/minimal_ssa_bug.hako
Normal 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
|
||||
}
|
||||
}
|
||||
28
apps/tests/minimal_ssa_bug_loop.hako
Normal file
28
apps/tests/minimal_ssa_bug_loop.hako
Normal 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
|
||||
}
|
||||
}
|
||||
24
apps/tests/minimal_ssa_skip_ws.hako
Normal file
24
apps/tests/minimal_ssa_skip_ws.hako
Normal 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
|
||||
}
|
||||
}
|
||||
1
apps/tests/stage1_using_minimal.hako
Normal file
1
apps/tests/stage1_using_minimal.hako
Normal file
@ -0,0 +1 @@
|
||||
using "foo/bar.hako" as Foo
|
||||
9
apps/tests/stage1_using_namespace.hako
Normal file
9
apps/tests/stage1_using_namespace.hako
Normal file
@ -0,0 +1,9 @@
|
||||
using std.collections
|
||||
using "path/to/module.hako" as MyModule
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
print("Hello")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
23
apps/tests/test_else_if_scope.hako
Normal file
23
apps/tests/test_else_if_scope.hako
Normal 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
|
||||
}
|
||||
}
|
||||
15
apps/tests/test_loop_conditional_assign.hako
Normal file
15
apps/tests/test_loop_conditional_assign.hako
Normal 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
|
||||
}
|
||||
}
|
||||
29
apps/tests/test_nested_scope_phi.hako
Normal file
29
apps/tests/test_nested_scope_phi.hako
Normal 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
|
||||
}
|
||||
}
|
||||
28
apps/tests/test_string_concat_phi.hako
Normal file
28
apps/tests/test_string_concat_phi.hako
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user