docs(stage1): Phase 25.1 - Stage-1 CLI ValueId(34) SSA バグ完全調査完了

🔍 根本原因特定:
- 問題箇所: stage1_cli.hako:111 の args null チェックパターン
  local argc = 0; if args != null { argc = args.size() }

- 発生条件:
  1. 深い制御フロー(BasicBlockId(12266) = 12,266ブロック)
  2. using chain 複雑さ(BuildBox → ParserBox → 50+ファイル)
  3. 多段階早期return(複数の支配フロンティアとPHIマージ)

- なぜShapeテストは通るか:
  - スタブ実装(複雑な外部Box呼び出しなし)
  - 単一ファイル(using chain なし)
  - シンプルなCFG(数十ブロック vs 12,266ブロック)

 解決策4案提示:
- Solution A: Fail-Fast Guard(最優先・最簡単)
  if args == null { args = new ArrayBox() }
  → PHI merge を 1定義に潰す

- Solution B: デバッグロジック抽出
  → 問題パターンを小さな関数に隔離

- Solution C: Rust Bridge修正
  → stage1_bridge.rs で常に非null保証

- Solution D: MIR Builder根治(長期)
  → SSA構築ロジック・PHI配置アルゴリズム改善

📋 成果物:
- 詳細調査レポート: docs/development/current/main/stage1_cli_ssa_valueid34_analysis.md
  - ValueId(34)の定義/使用ブロック解析
  - 呼び出しチェーン追跡
  - 制御フロー複雑度分析
  - 4つの解決策の詳細実装手順

- Shapeテスト追加: src/tests/stage1_cli_entry_ssa_smoke.rs
  - mir_stage1_cli_entry_like_pattern_verifies
  - mir_stage1_cli_stage1_main_shape_verifies
  - 構文とCFG形の正しさを検証(PASS)

🎯 技術的成果:
- MIRレベルのSSA追跡失敗メカニズムを完全解明
- 「テストは通るが実物は失敗」のギャップを構造的に特定
- 箱レベルでの実装可能な解決策を4案提示

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Task Assistant <task@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-21 07:00:05 +09:00
parent f9d100ce01
commit 3beddd6eb4
3 changed files with 553 additions and 0 deletions

View File

@ -14,6 +14,7 @@ pub mod mir_loopform_conditional_reassign;
pub mod mir_loopform_exit_phi;
pub mod mir_loopform_complex;
pub mod mir_stage1_using_resolver_verify;
pub mod stage1_cli_entry_ssa_smoke;
pub mod mir_stageb_like_args_length;
pub mod mir_stageb_loop_break_continue;
pub mod mir_value_kind; // Phase 26-A-5: ValueId型安全化統合テスト

View File

@ -0,0 +1,157 @@
use crate::ast::ASTNode;
use crate::mir::{MirCompiler, MirVerifier};
use crate::parser::NyashParser;
/// Minimal reproduction of Stage1Cli-style entry that calls `.size()` on an
/// argument-derived value. The goal is to mirror the Stage1 CLI pattern
/// (args/argc handling and a small loop) and ensure MIR/SSA stays consistent.
#[test]
fn mir_stage1_cli_entry_like_pattern_verifies() {
// Enable Stage3 and using so the parser accepts modern syntax.
std::env::set_var("NYASH_PARSER_STAGE3", "1");
std::env::set_var("NYASH_PARSER_ALLOW_SEMICOLON", "1");
std::env::set_var("NYASH_ENABLE_USING", "1");
std::env::set_var("HAKO_ENABLE_USING", "1");
let src = r#"
static box Stage1CliEntryLike {
main(args) {
// Guard args and argc before calling size() to keep SSA simple.
if args == null { return 97 }
local argc = 0
argc = args.size()
// Region+next_i style loop over argv for future extension.
local i = 0
loop(i < argc) {
local next_i = i + 1
local arg = "" + args.get(i)
if arg == "" { /* skip */ }
i = next_i
}
return argc
}
}
"#;
let ast: ASTNode = NyashParser::parse_from_string(src).expect("parse ok");
let mut mc = MirCompiler::with_options(false);
let cr = mc.compile(ast).expect("compile");
let mut verifier = MirVerifier::new();
if let Err(errors) = verifier.verify_module(&cr.module) {
for e in &errors {
eprintln!("[rust-mir-verify] {}", e);
}
panic!("MIR verification failed for Stage1CliEntryLike");
}
}
/// Shape test: clone the real stage1_main control flow (env toggles + dispatch),
/// but stub out emit/run methods, to check that the dispatcher itself does not
/// introduce SSA/PHI inconsistencies at MIR level.
#[test]
fn mir_stage1_cli_stage1_main_shape_verifies() {
std::env::set_var("NYASH_PARSER_STAGE3", "1");
std::env::set_var("NYASH_PARSER_ALLOW_SEMICOLON", "1");
std::env::set_var("NYASH_ENABLE_USING", "1");
std::env::set_var("HAKO_ENABLE_USING", "1");
let src = r#"
static box Stage1CliShape {
// Stub implementations to avoid IO/env bridge complexity.
emit_program_json(source) {
return "" + source
}
emit_mir_json(program_json) {
return "" + program_json
}
run_program_json(program_json, backend) {
// Just return a tag-based exit code for shape test.
if backend == null { backend = "vm" }
if backend == "vm" { return 0 }
if backend == "llvm" { return 0 }
if backend == "pyvm" { return 0 }
return 99
}
stage1_main(args) {
if env.get("STAGE1_CLI_DEBUG") == "1" {
local argc = 0; if args != null { argc = args.size() }
print("[stage1-cli/debug] stage1_main ENTRY: argc=" + ("" + argc) + " env_emits={prog=" + ("" + env.get("STAGE1_EMIT_PROGRAM_JSON")) + ",mir=" + ("" + env.get("STAGE1_EMIT_MIR_JSON")) + "} backend=" + ("" + env.get("STAGE1_BACKEND")))
}
{
local use_cli = env.get("NYASH_USE_STAGE1_CLI")
if use_cli == null || ("" + use_cli) != "1" {
if env.get("STAGE1_CLI_DEBUG") == "1" {
print("[stage1-cli/debug] stage1_main: NYASH_USE_STAGE1_CLI not set, returning 97")
}
return 97
}
}
// Prefer env-provided mode/source to avoid argv依存の不定値
local emit_prog = env.get("STAGE1_EMIT_PROGRAM_JSON")
local emit_mir = env.get("STAGE1_EMIT_MIR_JSON")
local backend = env.get("STAGE1_BACKEND"); if backend == null { backend = "vm" }
local source = env.get("STAGE1_SOURCE")
local prog_path = env.get("STAGE1_PROGRAM_JSON")
if emit_prog == "1" {
if source == null || source == "" {
print("[stage1-cli] emit program-json: STAGE1_SOURCE is required")
return 96
}
local ps = me.emit_program_json(source)
if ps == null { return 96 }
print(ps)
return 0
}
if emit_mir == "1" {
local prog_json = null
if prog_path != null && prog_path != "" {
// In real code this would read a file; here we just tag the path.
prog_json = "[from_file]" + prog_path
} else {
if source == null || source == "" {
print("[stage1-cli] emit mir-json: STAGE1_SOURCE or STAGE1_PROGRAM_JSON is required")
return 96
}
prog_json = me.emit_program_json(source)
}
if prog_json == null { return 96 }
local mir = me.emit_mir_json(prog_json)
if mir == null { return 96 }
print(mir)
return 0
}
// Default: run path (env-provided backend/source only)
if source == null || source == "" {
print("[stage1-cli] run: source path is required (set STAGE1_SOURCE)")
return 96
}
local prog_json = me.emit_program_json(source)
if prog_json == null { return 96 }
return me.run_program_json(prog_json, backend)
}
}
"#;
let ast: ASTNode = NyashParser::parse_from_string(src).expect("parse ok");
let mut mc = MirCompiler::with_options(false);
let cr = mc.compile(ast).expect("compile");
let mut verifier = MirVerifier::new();
if let Err(errors) = verifier.verify_module(&cr.module) {
for e in &errors {
eprintln!("[rust-mir-verify] {}", e);
}
panic!("MIR verification failed for Stage1CliShape.stage1_main");
}
}