# Phase 273: Plan Extractor (Pure) + PlanLowerer SSOT Status: ✅ P0/P1 completed (2025-12-22) Goal: - pattern 列挙の裾広がりを止める。 - pattern は "検出して Plan を返すだけ" に降格し、CFG/PHI/block/value の生成責務を 1 箇所に閉じ込める。 - P1: DomainPlan → CorePlan の 2層構造で "収束" を強める --- ## P1 完了 (2025-12-22) ### アーキテクチャ ``` DomainPlan (Pattern固有) ↓ PlanNormalizer (SSOT) CorePlan (固定語彙 - 構造ノードのみ) ↓ PlanVerifier (fail-fast) ↓ PlanLowerer MIR (block/value/phi) ``` ### SSOT Entry Point **Files**: - `src/mir/builder/control_flow/plan/mod.rs` - DomainPlan/CorePlan 型定義 - `src/mir/builder/control_flow/plan/normalizer.rs` - PlanNormalizer(DomainPlan → CorePlan) - `src/mir/builder/control_flow/plan/verifier.rs` - PlanVerifier(fail-fast 検証) - `src/mir/builder/control_flow/plan/lowerer.rs` - PlanLowerer(CorePlan → MIR) ### 原則 - Extractor は **pure**(builder 触り厳禁、DomainPlan を返すのみ) - Normalizer は **SSOT**(pattern 固有知識はここに集約) - CorePlan の式は **ValueId 参照のみ**(String 禁止 → 第2の言語処理系を作らない) - Lowerer は **pattern-agnostic**(CorePlan のみを処理) - terminator SSOT: Frag → emit_frag() --- ## CorePlan 固定語彙 ```rust pub enum CorePlan { Seq(Vec), Loop(CoreLoopPlan), If(CoreIfPlan), Effect(CoreEffectPlan), Exit(CoreExitPlan), } pub enum CoreEffectPlan { MethodCall { dst, object, method, args }, BinOp { dst, lhs, op, rhs }, Compare { dst, lhs, op, rhs }, Const { dst, value }, } ``` **増殖禁止ルール**: - ノード種別(variant)の追加は禁止 - `EffectPlan::ScanInit` のような scan専用 variant は禁止 - データ(フィールド、パラメータ)の追加は許容 --- ## P1 Implementation Summary **Files changed** (7 total): - Modified: `src/mir/builder/control_flow/plan/mod.rs` - DomainPlan/CorePlan 型定義 (~220 lines) - New: `src/mir/builder/control_flow/plan/normalizer.rs` - PlanNormalizer (~290 lines) - New: `src/mir/builder/control_flow/plan/verifier.rs` - PlanVerifier (~180 lines) - Modified: `src/mir/builder/control_flow/plan/lowerer.rs` - CorePlan 対応 (~250 lines) - Modified: `src/mir/builder/control_flow/joinir/patterns/pattern6_scan_with_init.rs` - DomainPlan 返却 - Modified: `src/mir/builder/control_flow/joinir/patterns/router.rs` - Normalizer + Verifier 経由 **Regression test**: - ✅ phase254_p0_index_of_vm.sh (fixed needle, forward scan) - ✅ phase258_p0_index_of_string_vm.sh (dynamic needle) --- ## LLVM harness の落とし穴(Phase 258 で露出) Phase 258 の `index_of_string`(dynamic needle)で、VM では正しいのに LLVM で `Result: 0` になるケースが露出した。 原因は Phase 273 P1 の本線(DomainPlan→CorePlan→emit_frag)ではなく、LLVM harness / AOT ランタイム側の “契約” だった。 ### 1) `params` を使わないと引数が silently に潰れる MIR JSON の `params`(ValueId の引数順)を使わず、heuristic で「未定義の use」を拾うと、 `box` フィールド等を見落とした場合に **v1 が arg0 に誤マップ**され、needle が haystack と同一扱いになる。 - Fix: `src/llvm_py/builders/function_lower.py` で `func_data["params"]` を SSOT として優先する ### 2) “raw integer vs handle” 衝突で `Result` が 0 になる AOT ランタイム(nyrt)は `ny_main()` の返り値が **raw i64** か **handle(i64)** かを区別できない。 正しい raw 返り値(例: `6`)が、たまたま生成済みの handle id と衝突すると、IntegerBox ではないため `Result: 0` になりうる。 - Fix: `crates/nyash_kernel/src/lib.rs` の exit_code 抽出で、handle が IntegerBox 以外なら raw i64 として扱う ## References - JoinIR SSOT overview: `docs/development/current/main/joinir-architecture-overview.md` - Frag SSOT: `docs/development/current/main/design/edgecfg-fragments.md` - Phase 272(Pattern6/7, Frag適用): `docs/development/current/main/phases/phase-272/README.md` ## Instructions - P0 Claude Code: `docs/development/current/main/phases/phase-273/P0-CLAUDE.md` - P1 Claude Code: `docs/development/current/main/phases/phase-273/P1-CLAUDE.md` ## Future Work (P2+) 1. **Pattern7/8/9 DomainPlan 追加**: Split, BoolPredicate 等を DomainPlan に追加 2. **Normalizer 拡張**: 各 DomainPlan → CorePlan 変換 3. **全 Pattern の Plan 化**: Pattern1-5 を段階的に Plan 化