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,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 + breakRegion+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 型 Loopinner は 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();
}

View 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();
}

View File

@ -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 {