- ✅ 最小再現ケース作成 - 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バグの構造を完全特定、デバッグ準備完了
172 lines
5.1 KiB
Rust
172 lines
5.1 KiB
Rust
//! 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();
|
|
}
|