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
|
||||
}
|
||||
}
|
||||
26
docs/development/analysis/minimal_ssa_bug_analysis.md
Normal file
26
docs/development/analysis/minimal_ssa_bug_analysis.md
Normal file
@ -0,0 +1,26 @@
|
||||
# Minimal SSA Bug Analysis — loop(1==1) + break exit PHI
|
||||
|
||||
## 何が起きていたか
|
||||
- Stage‑B / FuncScanner の `skip_whitespace` 系で、`loop(1 == 1) { ... break }` という形のループが exit PHI を壊し、`ValueId` 未定義エラー(`use of undefined value` / Dominator violation)が発生していた。
|
||||
- 原因: exit PHI 入力に「存在しない predecessor(header)」由来の値を含めていた。
|
||||
- `loop(1 == 1)` の場合、`header → exit` の CFG エッジは存在しない(exit は break 経路のみ)。
|
||||
- それにもかかわらず、`merge_exit_with_classification` が header 値を無条件で PHI 入力に足していたため、非支配ブロックからの値参照になり破綻。
|
||||
|
||||
## 最小再現
|
||||
- ファイル: `apps/tests/minimal_ssa_skip_ws.hako`
|
||||
- 構造: `loop(1 == 1)` の中で `if i >= n { break }` を先頭に置く形。break 以外に exit pred が無い。
|
||||
- Rustテスト: `src/tests/mir_loopform_conditional_reassign.rs::loop_constant_true_exit_phi_dominates`(今回新設)
|
||||
|
||||
## 修正内容
|
||||
- `src/mir/phi_core/loop_snapshot_merge.rs` の `merge_exit_with_classification` で、header が exit predecessor に含まれている場合にのみ header 値を PHI 入力へ追加するようガード。
|
||||
- CFG に存在しない predecessor 由来の値を PHI に入れないことで Dominator violation を解消。
|
||||
|
||||
## 追加したもの
|
||||
- 最小再現ハコ: `apps/tests/minimal_ssa_skip_ws.hako`
|
||||
- Rustテスト: `src/tests/mir_loopform_conditional_reassign.rs`
|
||||
- `loop_constant_true_exit_phi_dominates`(今回のバグ再現→修正確認用)
|
||||
- 将来拡張用に 2 本を `#[ignore]` で占位(条件付き再代入 / body-local 変数の exit PHI 観測)
|
||||
|
||||
## 次のステップ
|
||||
- `#[ignore]` テストを埋める: 条件付き再代入(`loop(i < n)`)や body-local 混在ケースの SSA 安定性を追加検証。
|
||||
- Stage‑B / UsingResolver の既存スモークにも `apps/tests/minimal_ssa_skip_ws.hako` を流し込んで回帰を防ぐ。
|
||||
85
src/tests/mir_loopform_complex.rs
Normal file
85
src/tests/mir_loopform_complex.rs
Normal file
@ -0,0 +1,85 @@
|
||||
//! LoopForm / Region+next_i 複雑パターン向けスモークテスト
|
||||
//!
|
||||
//! 目的:
|
||||
//! - continue + break 混在ループ
|
||||
//! - break + early-return 混在ループ
|
||||
//! - 外側ループ + 内側 Region 型ループのネスト
|
||||
//! を MIR Verifier ベースで「構造テスト」として押さえる。
|
||||
|
||||
use crate::mir::{MirCompiler, MirVerifier};
|
||||
use crate::parser::NyashParser;
|
||||
|
||||
fn setup_stage3_env() {
|
||||
std::env::set_var("NYASH_PARSER_STAGE3", "1");
|
||||
std::env::set_var("HAKO_PARSER_STAGE3", "1");
|
||||
std::env::set_var("NYASH_ENABLE_USING", "1");
|
||||
std::env::set_var("HAKO_ENABLE_USING", "1");
|
||||
std::env::set_var("NYASH_DISABLE_PLUGINS", "1");
|
||||
}
|
||||
|
||||
fn teardown_stage3_env() {
|
||||
std::env::remove_var("NYASH_PARSER_STAGE3");
|
||||
std::env::remove_var("HAKO_PARSER_STAGE3");
|
||||
std::env::remove_var("NYASH_ENABLE_USING");
|
||||
std::env::remove_var("HAKO_ENABLE_USING");
|
||||
std::env::remove_var("NYASH_DISABLE_PLUGINS");
|
||||
}
|
||||
|
||||
fn compile_module(src: &str) -> crate::mir::MirCompileResult {
|
||||
setup_stage3_env();
|
||||
let ast = NyashParser::parse_from_string(src).expect("parse ok");
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
mc.compile(ast).expect("compile ok")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mir_loopform_continue_break_scan_verify() {
|
||||
// Case C-ish: constant-true + continue/backedge + break(Region+next_i 型)
|
||||
let src = include_str!("../../apps/tests/loopform_continue_break_scan.hako");
|
||||
let compiled = compile_module(src);
|
||||
|
||||
let mut verifier = MirVerifier::new();
|
||||
if let Err(errors) = verifier.verify_module(&compiled.module) {
|
||||
for e in &errors {
|
||||
eprintln!("[mir-verify] {}", e);
|
||||
}
|
||||
teardown_stage3_env();
|
||||
panic!("mir_loopform_continue_break_scan_verify: MIR verification failed");
|
||||
}
|
||||
teardown_stage3_env();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mir_loopform_break_and_return_verify() {
|
||||
// Case: header-cond + break + early-return 混在
|
||||
let src = include_str!("../../apps/tests/loopform_break_and_return.hako");
|
||||
let compiled = compile_module(src);
|
||||
|
||||
let mut verifier = MirVerifier::new();
|
||||
if let Err(errors) = verifier.verify_module(&compiled.module) {
|
||||
for e in &errors {
|
||||
eprintln!("[mir-verify] {}", e);
|
||||
}
|
||||
teardown_stage3_env();
|
||||
panic!("mir_loopform_break_and_return_verify: MIR verification failed");
|
||||
}
|
||||
teardown_stage3_env();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mir_loopform_nested_region_verify() {
|
||||
// Case: 外側 Loop + 内側 Region 型 Loop(inner は constant-true + continue + break)
|
||||
let src = include_str!("../../apps/tests/loopform_nested_region.hako");
|
||||
let compiled = compile_module(src);
|
||||
|
||||
let mut verifier = MirVerifier::new();
|
||||
if let Err(errors) = verifier.verify_module(&compiled.module) {
|
||||
for e in &errors {
|
||||
eprintln!("[mir-verify] {}", e);
|
||||
}
|
||||
teardown_stage3_env();
|
||||
panic!("mir_loopform_nested_region_verify: MIR verification failed");
|
||||
}
|
||||
teardown_stage3_env();
|
||||
}
|
||||
|
||||
171
src/tests/mir_loopform_conditional_reassign.rs
Normal file
171
src/tests/mir_loopform_conditional_reassign.rs
Normal file
@ -0,0 +1,171 @@
|
||||
//! LoopForm exit PHI regression tests (conditional reassign / break)
|
||||
//!
|
||||
//! 目的:
|
||||
//! - loop(1 == 1) + break 経路で exit PHI が壊れて ValueId 未定義になる既知バグを捕まえる。
|
||||
//! - 将来: 条件付き再代入や body-local 変数が混在するケースを拡張(現状は #[ignore])。
|
||||
|
||||
use crate::mir::{MirCompiler, MirVerifier};
|
||||
use crate::parser::NyashParser;
|
||||
|
||||
fn setup_stage3_env() {
|
||||
std::env::set_var("NYASH_PARSER_STAGE3", "1");
|
||||
std::env::set_var("HAKO_PARSER_STAGE3", "1");
|
||||
std::env::set_var("NYASH_ENABLE_USING", "1");
|
||||
std::env::set_var("HAKO_ENABLE_USING", "1");
|
||||
std::env::set_var("NYASH_DISABLE_PLUGINS", "1");
|
||||
}
|
||||
|
||||
fn teardown_stage3_env() {
|
||||
std::env::remove_var("NYASH_PARSER_STAGE3");
|
||||
std::env::remove_var("HAKO_PARSER_STAGE3");
|
||||
std::env::remove_var("NYASH_ENABLE_USING");
|
||||
std::env::remove_var("HAKO_ENABLE_USING");
|
||||
std::env::remove_var("NYASH_DISABLE_PLUGINS");
|
||||
}
|
||||
|
||||
fn compile_module(src: &str) -> crate::mir::MirCompileResult {
|
||||
setup_stage3_env();
|
||||
let ast = NyashParser::parse_from_string(src).expect("parse ok");
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
mc.compile(ast).expect("compile ok")
|
||||
}
|
||||
|
||||
// [LoopForm-Test] Case B: constant-true+break-only
|
||||
#[test]
|
||||
fn loop_constant_true_exit_phi_dominates() {
|
||||
// Repro: loop(1 == 1) + break only (header is NOT an exit predecessor)
|
||||
// This used to create exit PHIs with header inputs from non-predecessor blocks → dominator violation.
|
||||
let src = include_str!("../../apps/tests/minimal_ssa_skip_ws.hako");
|
||||
let compiled = compile_module(src);
|
||||
|
||||
let mut verifier = MirVerifier::new();
|
||||
if let Err(errors) = verifier.verify_module(&compiled.module) {
|
||||
for e in &errors {
|
||||
eprintln!("[mir-verify] {}", e);
|
||||
}
|
||||
teardown_stage3_env();
|
||||
panic!("loop_constant_true_exit_phi_dominates: MIR verification failed");
|
||||
}
|
||||
teardown_stage3_env();
|
||||
}
|
||||
|
||||
// [LoopForm-Test] Case A: header+break
|
||||
#[test]
|
||||
fn loop_conditional_reassign_exit_phi_header_and_break() {
|
||||
// Case A: 通常の loop(i < n) で header→exit と body→exit の両方が存在するケース。
|
||||
// ここで PHI が壊れていないことを確認し、今回の修正が既存パターンを regress していないことを見る。
|
||||
let src = r#"
|
||||
static box Main {
|
||||
loop_case(s) {
|
||||
local i = 0
|
||||
local n = s.length()
|
||||
loop(i < n) {
|
||||
if i >= n { break }
|
||||
i = i + 1
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
main(args) {
|
||||
local s = "abc"
|
||||
local r = Main.loop_case(s)
|
||||
print(r)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let compiled = compile_module(src);
|
||||
|
||||
let mut verifier = MirVerifier::new();
|
||||
if let Err(errors) = verifier.verify_module(&compiled.module) {
|
||||
for e in &errors {
|
||||
eprintln!("[mir-verify] {}", e);
|
||||
}
|
||||
teardown_stage3_env();
|
||||
panic!("loop_conditional_reassign_exit_phi_header_and_break: MIR verification failed");
|
||||
}
|
||||
teardown_stage3_env();
|
||||
}
|
||||
|
||||
// [LoopForm-Test] Case C: body-local (BodyLocalInternal)
|
||||
#[test]
|
||||
fn loop_body_local_exit_phi_body_only() {
|
||||
// Case C: body-local 変数が break 経路にだけ存在し、header には存在しないケース。
|
||||
// Option C の分類により、こうした BodyLocalInternal には exit PHI が張られず、
|
||||
// MIR 検証が通ることを確認する。
|
||||
let src = r#"
|
||||
static box Main {
|
||||
loop_case(s) {
|
||||
local i = 0
|
||||
local n = s.length()
|
||||
loop(1 == 1) {
|
||||
if i >= n { break }
|
||||
local temp = s.substring(i, i + 1)
|
||||
if temp == "x" { break } else { i = i + 1 }
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
main(args) {
|
||||
local s = "abc"
|
||||
local r = Main.loop_case(s)
|
||||
print(r)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let compiled = compile_module(src);
|
||||
|
||||
let mut verifier = MirVerifier::new();
|
||||
if let Err(errors) = verifier.verify_module(&compiled.module) {
|
||||
for e in &errors {
|
||||
eprintln!("[mir-verify] {}", e);
|
||||
}
|
||||
teardown_stage3_env();
|
||||
panic!("loop_body_local_exit_phi_body_only: MIR verification failed");
|
||||
}
|
||||
teardown_stage3_env();
|
||||
}
|
||||
|
||||
// [LoopForm-Test] Case D: continue+break (continue_merge → header → exit)
|
||||
#[test]
|
||||
fn loop_continue_merge_header_exit() {
|
||||
// Case D: continue 文を含むループで、continue_merge → header → exit の組み合わせを検証。
|
||||
// continue_merge ブロックで PHI を生成し、header PHI に正しく伝播することを確認。
|
||||
let src = r#"
|
||||
static box Main {
|
||||
loop_case(s) {
|
||||
local i = 0
|
||||
local n = s.length()
|
||||
loop(i < n) {
|
||||
local ch = s.substring(i, i + 1)
|
||||
if ch == " " {
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if ch == "x" { break }
|
||||
i = i + 1
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
main(args) {
|
||||
local s = " x "
|
||||
local r = Main.loop_case(s)
|
||||
print(r)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let compiled = compile_module(src);
|
||||
|
||||
let mut verifier = MirVerifier::new();
|
||||
if let Err(errors) = verifier.verify_module(&compiled.module) {
|
||||
for e in &errors {
|
||||
eprintln!("[mir-verify] {}", e);
|
||||
}
|
||||
teardown_stage3_env();
|
||||
panic!("loop_continue_merge_header_exit: MIR verification failed");
|
||||
}
|
||||
teardown_stage3_env();
|
||||
}
|
||||
@ -5,8 +5,8 @@
|
||||
* Focus: predecessor tracking and PHI input generation for break statements
|
||||
*/
|
||||
|
||||
use crate::parser::NyashParser;
|
||||
use crate::mir::{MirCompiler, MirVerifier};
|
||||
use crate::parser::NyashParser;
|
||||
|
||||
#[test]
|
||||
fn test_loopform_exit_phi_single_break() {
|
||||
@ -33,8 +33,7 @@ static box TestExitPhi {
|
||||
println!("=== Test: Single break statement ===");
|
||||
|
||||
// Parse
|
||||
let ast = NyashParser::parse_from_string(src)
|
||||
.expect("parse failed");
|
||||
let ast = NyashParser::parse_from_string(src).expect("parse failed");
|
||||
|
||||
// Compile
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
@ -46,8 +45,12 @@ static box TestExitPhi {
|
||||
eprintln!("Entry block: {:?}", func.entry_block);
|
||||
eprintln!("Total blocks: {}", func.blocks.len());
|
||||
for (bid, block) in &func.blocks {
|
||||
eprintln!(" Block {:?}: {} instructions, successors={:?}",
|
||||
bid, block.instructions.len(), block.successors);
|
||||
eprintln!(
|
||||
" Block {:?}: {} instructions, successors={:?}",
|
||||
bid,
|
||||
block.instructions.len(),
|
||||
block.successors
|
||||
);
|
||||
if *bid == crate::mir::BasicBlockId(10) {
|
||||
eprintln!(" BB10 instructions:");
|
||||
for inst in &block.instructions {
|
||||
|
||||
Reference in New Issue
Block a user