diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 5b66f929..60d772c3 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -31,6 +31,20 @@ - DigitPos promotion を二重値化し、`digit_pos` から boolean carrier `is_digit_pos`(ConditionOnly)と integer carrier `digit_value`(LoopState)を生成。 - UpdateEnv で `digit_pos` 解決時に `digit_value` を優先し、NumberAccumulation(`result = result * 10 + digit_pos`)と break 条件の両方で DigitPos パターンが安全に利用可能に。 - 現在: `cargo test --release --lib` で 931/931 テスト PASS(既知 FAIL なし)。 +- Phase 28-NORM-P2(dev-only): + - Normalized JoinIR のミニ実装を Pattern1 に続き Pattern2 最小ケースまで拡張(Structured→Normalized→Structured を比較)。 + - 対応外の Structured JoinModule では normalize_pattern2_minimal が Fail-Fast するようガードを追加し、normalized_dev テストで固定。 +- Phase 29-NORM-P2-APPLY(dev-only): + - Phase 34 の break fixture(i/acc/n の単純 break ループ)を Structured→Normalized→Structured の往復に通し、VM 実行結果が Structured 直経路と一致することを dev テストで固定。 + - ガードは 3 パラメータまで緩和しつつ、DigitPos/Trim などの重いキャリアはまだ非対応のまま。 +- Phase 30-NORM-P2-DEV-RUN(dev-only): + - JoinIR runner に `NYASH_JOINIR_NORMALIZED_DEV_RUN=1` を追加し、Pattern1/2 ミニケースだけ Structured→Normalized→Structured を挟んで dev 実行できるようにした(`normalized_dev` + debug 限定)。通常経路(Structured→MIR)は不変。 +- Phase 31-NORM-JP-MINI(dev-only): + - JsonParser 系のシンプルな P2 ループ(skip_whitespace ミニ fixture)を Structured→Normalized→Structured 経由でも実行し、runner dev スイッチの比較テストで Structured 直経路と一致することを固定。 +- Phase 32-NORM-CANON-PREP(dev-only): + - JoinIR→MIR ブリッジの入口を `bridge_joinir_to_mir` に一本化し、normalized_dev スイッチ(feature + env)で Structured→Normalized→Structured の dev roundtrip を切り替える準備を整えた。P1/P2/JP mini の比較テストも VM ブリッジ経路で追加。 +- Phase 33-NORM-CANON-TEST(dev-only): + - P1/P2(Phase 34 break fixture)/JsonParser skip_ws mini について、normalized_dev ON 時は shape_guard 経由で必ず Normalized roundtrip を通すようブリッジと runner を固めた。normalized_joinir_min.rs の runner/VM 比較テストを拡張し、Normalized が壊れたら dev スイートが必ず赤になるようにした(本番 CLI は従来どおり Structured→MIR)。 ### 1. いまコード側で意識しておきたいフォーカス diff --git a/Cargo.toml b/Cargo.toml index 50b00ba1..6aae8c9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,8 @@ gui-examples = ["gui"] all-examples = ["gui-examples"] dynamic-file = [] wasm-backend = ["dep:wasmtime", "dep:wabt"] +# Dev-only normalized JoinIR experiment (Pattern1) +normalized_dev = [] # TLV C shim wiring (default OFF): enables optional dependency `nyash-tlv` tlv-shim = ["dep:nyash-tlv"] # Core C shims (design-stage; default OFF) diff --git a/docs/development/current/main/joinir-architecture-overview.md b/docs/development/current/main/joinir-architecture-overview.md index 462a857f..dc5df513 100644 --- a/docs/development/current/main/joinir-architecture-overview.md +++ b/docs/development/current/main/joinir-architecture-overview.md @@ -1106,7 +1106,65 @@ Normalized JoinIR では、制御構造を次の 3 要素だけで表現する これにより、現在 JoinIR 層で苦労している「PHI 配線」「exit_bindings/jump_args 整合性」「評価順のねじれ」は、 Normalized JoinIR 側では「Env フィールドの更新順」と「どの継続を呼ぶか」に還元される想定だよ。 -### 3.3 Phase 26-H のフェーズ分割(案) +### 3.3 Normalized JoinIR 導入のメリットと初期コスト + +Normalized JoinIR を 1 段挟むと、開発の手触りがどう変わるかをここでまとめておくよ。 + +#### 3.3.1 楽になるポイント + +- **デバッグ時の「問いの分離」** + - Structured JoinIR だけで `_parse_number` / `_atoi` を追うときは、常に同時に 4 つくらいの問いを抱えることになる: + 1. Pattern 判定は正しいか(P1/P2/P3/P4/P5) + 2. CarrierInfo / LoopState / LoopLocalZero / FromHost の設計は妥当か + 3. 評価順(header → body-init → break → updates → tail)は正しいか + 4. PHI / exit_bindings / jump_args / variable_map の整合は取れているか + - Normalized JoinIR を挟むと、これが層ごとに分割される: + - 上(Structured): Pattern 判定 + CarrierInfo/Condition/Update の設計 + - 中(Normalized): EnvLayout + step/kont 合成(制御骨格) + - 下(Bridge): TailCall+Env から MIR/VM の PHI/branch を作る + - これにより、「これは Pattern/Carrier 問題」「これは Env+継続の組み立て問題」「これは Bridge 問題」と、デバッグ時にレイヤ単位で切り分けやすくなる。 + +- **PHI まわりの思考負荷軽減** + - Structured JoinIR では、LoopHeader PHI / Exit PHI / Select 展開 PHI と、exit_bindings/jump_args/variable_map 再接続が絡み合う。 + - Normalized フェーズでは、キャリアや DigitPos/NumberAccumulation などはすべて Env フィールドになり、PHI は JoinIR’ には現れない: + - 「どこから値が来たか?」は「どの Env フィールドがどの順番で更新されたか」を見るだけでよくなる。 + - PHI/exit_bindings は Bridge 側の責務として明示的に分離される。 + +- **while/if/break/continue/return の骨格が共通化される** + - Pattern1–5 ごとに似たロジックが散らばりがちな現在と違い、Normalized では: + - ループ: `loop_step(env, k_exit)` + `loop_body(env, k_exit)` + - if: 条件判定 → then/else 継続 → join 継続 + - break: `TailCallKont(k_exit, env)` + - continue: `TailCallFn(loop_step, env', k_exit)` + - こうした「世界共通のテンプレ」を 1 箱にまとめておけるので、新しい Pattern や JsonParser/selfhost のループを追加するときも、構造箱(Structured)側だけで頑張ればよくなる。 + +- **「1箱 = 1質問」を守りやすくなる** + - Structured JoinIR では、PatternLowerer が一時的に「構造+評価順+PHI 配線+exit_bindings」を抱える瞬間がある。 + - Normalized を挟むと: + - Structured: 「どの値を Env に載せるか」を決める箱 + - Normalized: 「Env と継続にどう分解するか」を決める箱 + - Bridge: 「TailCall+Env を MIR/VM にどう落とすか」を決める箱 + - という分担にできるので、「この箱は何の質問に答えるか」が清潔に保ちやすくなる。 + +#### 3.3.2 初期コストと注意点 + +- **新しい IR モデルを 1 セット覚える必要がある** + - Env+Fn+Kont+TailCall の世界観を JoinIR 層の前提として追加することになる。 + - 一度定着すれば Structured より思考負荷は下がるが、導入初期は「型・不変条件・ダンプの読み方」に慣れるまで少しコストが乗る。 + +- **移行期間中は 2 経路の比較が発生する** + - AST → JoinIR(Structured) → MIR + - AST → JoinIR(Structured) → JoinIR(Normalized) → MIR + - の 2 経路をしばらく併走させ、dev で結果比較をするフェーズが必要になる。 + - Phase 26-H / 28-NORM-P2 のように「小さいサブセットから比較用にだけ回す」方針で進めることで、コストは制御可能にする。 + +- **導入フェーズごとに Fail-Fast とテストが必須** + - Structured→Normalized は対応範囲外の Structured JoinModule については必ず Fail-Fast(panic/Err)し、サイレントに正規化を試みない。 + - `normalized_dev` feature 配下で、Structured↔Normalized↔Structured の roundtrip と VM 実行結果比較を常に追加していく運用にする。 + +全体として、短期的には「新しいレイヤと比較テストを抱えるコスト」が増えるけれど、中長期では Pattern2〜4 や JsonParser 系の「評価順+PHI+DigitPos/num_str」が絡むループで、デバッグと設計の負荷をかなり下げることを狙った設計だよ。 + +### 3.4 Phase 26-H のフェーズ分割(案) この正規化レイヤを一度に入れるのは重いので、Phase 26-H ラインとして小さく段階分けして進める方針だよ。 (番号は仮で、実際の Phase 番号は current の進行に合わせて調整する想定) @@ -1136,6 +1194,41 @@ Normalized JoinIR 側では「Env フィールドの更新順」と「どの継 各サブフェーズでは「既存意味論を変えない」「Fail-Fast 原則を維持する」「新旧経路の比較テストを先に用意する」をガードとして運用するよ。 詳細な API/型設計は、Phase 26-H.A/B の中で `EnvLayoutBox` / `LoopStepSynthesizer` / `JpVerifier` 等の箱として段階的に固めていく想定。 -### 3.4 Phase 27-CLEAN – Pattern2–4 の軽量整理 +### 3.5 Phase 27-CLEAN – Pattern2–4 の軽量整理 - Pattern2〜4/loop_with_break_minimal まわりで可視性・ログ・補助関数を整理し、joinir_dev フラグ配下のデバッグログに寄せる。意味論は変えずに「読みやすさ」「追いやすさ」を優先するクリーンアップフェーズだよ。 + +### 3.6 Phase 28-NORM-P2 – Normalized JoinIR プロトタイプ拡張(dev-only) + +- Phase 26-H で用意した Normalized JoinIR の極小サブセットを、Pattern1 に続いて Pattern2(最小 break ループ)まで拡張。 +- Structured → Normalized → Structured の往復と VM 実行比較を dev フィーチャ (`normalized_dev` + debug) でテスト済み。 +- 対象は joinir_min_loop 相当の「ループ変数1つ+breakのみ」のミニケースに限定し、本番経路(Structured→MIR)は不変。 +- normalize_pattern2_minimal は対応外の Structured JoinModule では Fail-Fast するようにガードを追加し、対応範囲をテストで固定。 + +### 3.7 Phase 29-NORM-P2-APPLY – Pattern2 実ループへの dev 適用 + +- Phase 34 の fixture `loop_frontend_break.program.json`(`i/acc/n` のシンプル break ループ)を Structured→Normalized→Structured の往復経路に載せ、VM 実行結果が Structured 直経路と一致することを dev テストで確認。 +- `normalize_pattern2_minimal` のガードを 3 パラメータ(loop var + acc + host)まで許容する形に緩めつつ、DigitPos/Trim などの heavy carrier は依然として非対応に固定。 +- すべて `normalized_dev` feature + debug_assertions 配下の実験経路に閉じ、本番 Structured→MIR パスの挙動は不変。 + +### 3.8 Phase 30-NORM-P2-DEV-RUN – runner で Normalized を試走(dev) + +- JoinIR runner に dev 専用の `NYASH_JOINIR_NORMALIZED_DEV_RUN=1` スイッチを追加し、Pattern1/2 のミニケースだけ Structured→Normalized→Structured を噛ませてから実行できるようにした(`normalized_dev` feature + debug ビルド限定)。 +- runner 経路でも Structured 直実行との stdout/結果が一致することをテスト(loop_min_while と Phase 34 break fixture)で確認。フラグ OFF 時の挙動は従来と同じ。 + +### 3.9 Phase 31-NORM-JP-MINI – JsonParser ミニ P2 を Normalized dev 経由で試走 + +- JsonParser 系のシンプルな P2 ループ(skip_whitespace 簡易版)を Structured→Normalized→Structured の dev ランナー経路に載せ、通常経路との実行結果一致を比較。 +- `jsonparser_skip_ws_mini.program.json`(docs/private/roadmap2/phases/normalized_dev/fixtures 配下)由来の JoinModule を使い、`NYASH_JOINIR_NORMALIZED_DEV_RUN=1` + `normalized_dev` + debug 限定で切替可能にした。 +- 本番経路(Structured→MIR)は引き続き不変で、Normalized は dev 比較専用のまま。 + +### 3.10 Phase 32-NORM-CANON-PREP – Normalized 本番導入の下地づくり + +- JoinIR→MIR ブリッジに Structured/Normalized の入口を分けた `bridge_joinir_to_mir_*` を用意し、conversion pipeline / VM ランナーがこの 1 箇所で dev roundtrip を切替できるように整理。 +- Normalized dev スイッチを `normalized_dev_enabled()` に集約(feature `normalized_dev` + `NYASH_JOINIR_NORMALIZED_DEV_RUN=1`)。P1/P2 ミニ + JsonParser mini で env ON/OFF の比較テストを追加し、いつでも canonical route に昇格できる状態を固めた。 + +### 3.11 Phase 33-NORM-CANON-TEST – P1/P2/JP mini をテスト必須ラインへ + +- `bridge_joinir_to_mir` / JoinIR runner は `shape_guard` で P1/P2 ミニ + JsonParser skip_ws mini を検知した場合、`normalized_dev_enabled()` が ON なら必ず Structured→Normalized→Structured の dev roundtrip を経由(正規化失敗は dev panic)。未対応形状は静かに Structured 直通。 +- tests/normalized_joinir_min.rs を Phase 33 前提に拡張し、P1/P2/JP mini の runner/VM 比較テストを env ON で実行。Normalized が壊れればこのスイートが必ず赤になる構造にした(feature OFF の CI は従来どおり無関係)。 +- 本番 CLI 挙動は Structured→MIR のまま維持しつつ、Normalized を canonical に昇格させる前段階として dev テストで SSOT 相当の役割を担わせている。 diff --git a/docs/development/current/main/phase26-HC-normalized-pattern1-bridge.md b/docs/development/current/main/phase26-HC-normalized-pattern1-bridge.md new file mode 100644 index 00000000..3629580f --- /dev/null +++ b/docs/development/current/main/phase26-HC-normalized-pattern1-bridge.md @@ -0,0 +1,65 @@ +Status: Active +Scope: Phase 26‑H.C ― Normalized JoinIR (Pattern1) を MIR に落とす最小ブリッジの設計と比較テスト計画(dev専用)。 + +# Phase 26‑H.C 指示書 — Normalized→MIR ブリッジ(Pattern1 最小)+比較テスト + +## ゴール +- Normalized JoinIR(NormalizedModule)から MIR への「本物のブリッジ」を、Pattern1 最小ケースだけで実装する。 +- Structured→MIR と Structured→Normalized→MIR の結果が一致することをテストで確認する(dev 専用、CLI はまだ触らない)。 + +## A. Normalized→Structured or Normalized→MIR の経路方針 +- Option 1: Normalized→Structured→既存 MIR ブリッジ + - 長所: 既存の JoinIR→MIR パイプラインを再利用できる。 + - 短所: フェーズが一段増える(Normalized→Structured が joinir→joinir の一歩になる)。 +- Option 2: Normalized→MIR を直接作るミニブリッジ(Pattern1 限定) **←今回これを優先** + - 長所: TailCall/Env をそのまま MIR に落とす感触を掴める。 + - 短所: 一部ロジックが JoinIR→MIR と重複する。 +- どちらを選んでも「Pattern1 最小ケース限定」「テスト専用 helper」とする(本番パスへは配線しない)。今回は Option 2 で進め、必要があれば Option 1 のスケルトンも残す。 + +## B. Normalized→Structured ミニ変換(Option 1 採用時のメモ) +- 追加場所: `src/mir/join_ir/normalized.rs` +- API 例: `pub fn normalize_pattern1_to_structured(norm: &NormalizedModule) -> JoinModule` +- 制約: `norm.phase == JoinIrPhase::Normalized`、関数は 1 つ(loop_step + 仮の k_exit 程度)を想定。 +- 変換の要点: + - EnvLayout の fields から JoinFunction の params を再構成(最小なら1引数でも可)。 + - `JpInst::Let` → `JoinInst::Compute(MirLikeInst::Const/BinOp/UnaryOp/Compare)` に戻す。 + - TailCallFn/TailCallKont/If を Pattern1 が生成していた `JoinInst::Call/Jump/Ret` 相当に戻す。 + - `NormalizedModule.entry` を `JoinModule.entry` に写す。 +- `NormalizedModule.structured_backup` は比較用に残すが、ブリッジでは本体から再構成する。 + +## C. Normalized→MIR ミニブリッジ(今回の主経路: Option 2) +- 追加ファイル案: `src/mir/join_ir/lowering/normalized_pattern1_to_mir.rs` +- API 例: + ```rust + pub fn lower_normalized_pattern1_to_mir(norm: &NormalizedModule) -> crate::mir::Module { ... } + ``` +- 最低限やること: + - EnvLayout の 1 フィールドを MIR のループ変数に対応させる。 + - `JpFunction` 本体を 1 ブロック or 小ブロック列に変換(Let→MIR Assign/Compute、TailCallFn→ループ末尾 Jump 等)。 + - Pattern1 の `loop_min_while` 相当が生成していた MIR と構造的に一致するかをテストで確認。 +- ガード: dev/テスト専用 helper から明示的に呼ぶ。ランナー/CLI には配線しない。 + +## D. 比較用テストの追加 +- テストファイル案: `tests/normalized_pattern1_bridge.rs` +- シナリオ: + 1. Structured JoinIR (Pattern1: loop_min_while 相当) を既存 lowerer で生成。 + 2. そのコピーを `normalize_pattern1_minimal` に通し NormalizedModule を得る。 + 3. 既存経路: Structured → 既存 JoinIR→MIR ブリッジ → 実行 or MIR dump。 + 4. 新経路: Structured → Normalized → (C のブリッジ)→ MIR → 実行 or MIR dump。 + 5. 比較: + - 実行結果一致(RC + stdout)。 + - 余裕があれば MIR の基本ブロック数や命令種も比較し、構造乖離がないことを確認。 +- テスト名例: + - `test_normalized_pattern1_minimal_roundtrip` + - `test_normalized_pattern1_exec_result_matches_structured` + +## E. ガードと完了条件 +- ガード: + - 新ブリッジはテスト専用(ランナー/CLI からは呼ばない)。 + - JoinModule.phase が Structured のままでも既存経路は従来どおり動作。 + - Normalized 経路は dev/テスト専用 helper からのみ呼ぶ。 +- 完了条件: + - NormalizedModule から MIR を生成する経路(Option 2)が 1 本通る。 + - Pattern1 最小ケースで Structured→MIR と Structured→Normalized→MIR が同じ結果になるテストが緑。 + - 既存 `cargo test --release` が 0 FAIL のまま。 + diff --git a/src/ast.rs b/src/ast.rs index 17382802..52b8b0b5 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -527,8 +527,8 @@ pub enum ASTNode { /// 値・型は右辺と同じ、副作用として左辺に代入 /// 使用例: local y = (x = x + 1), if (x = next()) != null { } GroupedAssignmentExpr { - lhs: String, // 変数名 - rhs: Box, // 右辺式 + lhs: String, // 変数名 + rhs: Box, // 右辺式 span: Span, }, diff --git a/src/backend/mir_interpreter/exec.rs b/src/backend/mir_interpreter/exec.rs index 839dc001..8f9949a1 100644 --- a/src/backend/mir_interpreter/exec.rs +++ b/src/backend/mir_interpreter/exec.rs @@ -68,16 +68,14 @@ impl MirInterpreter { }) .unwrap_or(1_000_000); let mut steps: u64 = 0; - let trace_log_path: Option = std::env::var("NYASH_VM_TRACE_LOG") - .ok() - .map(|v| { - let trimmed = v.trim(); - if trimmed.is_empty() || trimmed == "1" { - "__mir__.log".to_string() - } else { - v - } - }); + let trace_log_path: Option = std::env::var("NYASH_VM_TRACE_LOG").ok().map(|v| { + let trimmed = v.trim(); + if trimmed.is_empty() || trimmed == "1" { + "__mir__.log".to_string() + } else { + v + } + }); loop { steps += 1; diff --git a/src/backend/mir_interpreter/handlers/calls/method.rs b/src/backend/mir_interpreter/handlers/calls/method.rs index 115f7200..b9094942 100644 --- a/src/backend/mir_interpreter/handlers/calls/method.rs +++ b/src/backend/mir_interpreter/handlers/calls/method.rs @@ -313,7 +313,10 @@ impl MirInterpreter { ("ConsoleBox", 400) => { // log/println if let VMValue::BoxRef(bx) = receiver { - if let Some(console) = bx.as_any().downcast_ref::() { + if let Some(console) = bx + .as_any() + .downcast_ref::() + { let message = if args.len() > 1 { self.reg_load(args[1])?.to_string() } else if args.len() > 0 { @@ -330,7 +333,10 @@ impl MirInterpreter { ("ConsoleBox", 401) => { // warn if let VMValue::BoxRef(bx) = receiver { - if let Some(console) = bx.as_any().downcast_ref::() { + if let Some(console) = bx + .as_any() + .downcast_ref::() + { let message = if args.len() > 1 { self.reg_load(args[1])?.to_string() } else if args.len() > 0 { @@ -347,7 +353,10 @@ impl MirInterpreter { ("ConsoleBox", 402) => { // error if let VMValue::BoxRef(bx) = receiver { - if let Some(console) = bx.as_any().downcast_ref::() { + if let Some(console) = bx + .as_any() + .downcast_ref::() + { let message = if args.len() > 1 { self.reg_load(args[1])?.to_string() } else if args.len() > 0 { @@ -364,7 +373,10 @@ impl MirInterpreter { ("ConsoleBox", 403) => { // clear if let VMValue::BoxRef(bx) = receiver { - if let Some(console) = bx.as_any().downcast_ref::() { + if let Some(console) = bx + .as_any() + .downcast_ref::() + { console.clear(); return Ok(VMValue::Void); } @@ -405,7 +417,10 @@ impl MirInterpreter { // Plugin Box methods (slot >= 1000) (_, slot) if slot >= 1000 => { if let VMValue::BoxRef(bx) = receiver { - if let Some(_p) = bx.as_any().downcast_ref::() { + if let Some(_p) = bx + .as_any() + .downcast_ref::() + { let host = crate::runtime::plugin_loader_unified::get_global_plugin_host(); let _host = host.read().unwrap(); let _argv = self.load_args_as_boxes(args)?; @@ -447,22 +462,20 @@ impl MirInterpreter { // 2. Lookup type in TypeRegistry and get slot // Note: Try exact arity first, then try with args.len()-1 (in case receiver is duplicated in args) - let slot = crate::runtime::type_registry::resolve_slot_by_name( - type_name, - method, - args.len(), - ).or_else(|| { - // Fallback: try with one less argument (receiver might be in args) - if args.len() > 0 { - crate::runtime::type_registry::resolve_slot_by_name( - type_name, - method, - args.len() - 1, - ) - } else { - None - } - }); + let slot = + crate::runtime::type_registry::resolve_slot_by_name(type_name, method, args.len()) + .or_else(|| { + // Fallback: try with one less argument (receiver might be in args) + if args.len() > 0 { + crate::runtime::type_registry::resolve_slot_by_name( + type_name, + method, + args.len() - 1, + ) + } else { + None + } + }); if let Some(slot) = slot { // 3. Use unified dispatch @@ -489,9 +502,8 @@ impl MirInterpreter { if let Some(arg_id) = args.get(0) { let ch = self.reg_load(*arg_id)?.to_string(); let c = ch.chars().next().unwrap_or('\0'); - let is_alpha = ('A'..='Z').contains(&c) - || ('a'..='z').contains(&c) - || c == '_'; + let is_alpha = + ('A'..='Z').contains(&c) || ('a'..='z').contains(&c) || c == '_'; return Ok(VMValue::Bool(is_alpha)); } else { return Err(self.err_invalid("is_alpha requires 1 argument")); @@ -520,12 +532,7 @@ impl MirInterpreter { let host = crate::runtime::plugin_loader_unified::get_global_plugin_host(); let host = host.read().unwrap(); let argv = self.load_args_as_boxes(args)?; - match host.invoke_instance_method( - &p.box_type, - method, - p.inner.instance_id, - &argv, - ) { + match host.invoke_instance_method(&p.box_type, method, p.inner.instance_id, &argv) { Ok(Some(ret)) => return Ok(VMValue::from_nyash_box(ret)), Ok(None) => return Ok(VMValue::Void), Err(e) => { diff --git a/src/backend/mir_interpreter/mod.rs b/src/backend/mir_interpreter/mod.rs index 9ffce625..9b10a4b5 100644 --- a/src/backend/mir_interpreter/mod.rs +++ b/src/backend/mir_interpreter/mod.rs @@ -253,11 +253,7 @@ impl MirInterpreter { } }; - if std::env::var("NYASH_EMIT_MIR_TRACE") - .ok() - .as_deref() - == Some("1") - { + if std::env::var("NYASH_EMIT_MIR_TRACE").ok().as_deref() == Some("1") { if let Some(name) = chosen_name { eprintln!("[vm/entry] main={}", name); } diff --git a/src/backend/mir_interpreter/static_box_registry.rs b/src/backend/mir_interpreter/static_box_registry.rs index c23d3219..73adcac8 100644 --- a/src/backend/mir_interpreter/static_box_registry.rs +++ b/src/backend/mir_interpreter/static_box_registry.rs @@ -194,9 +194,7 @@ impl StaticBoxRegistry { /// 登録済み/検出済みの静的Box名一覧 pub fn all_box_names(&self) -> impl Iterator { - self.declarations - .keys() - .chain(self.detected_boxes.iter()) + self.declarations.keys().chain(self.detected_boxes.iter()) } /// declarations への直接アクセス (既存コードとの互換性) @@ -250,7 +248,7 @@ mod tests { "JsonParserBox.parse/1".to_string(), "JsonParserBox.toString/0".to_string(), "ProgramJSONBox.get/1".to_string(), - "Main.main/0".to_string(), // 除外される + "Main.main/0".to_string(), // 除外される "StringBox.length/0".to_string(), // 除外される ]; diff --git a/src/box_factory/mod.rs b/src/box_factory/mod.rs index ec912a0d..05d56485 100644 --- a/src/box_factory/mod.rs +++ b/src/box_factory/mod.rs @@ -190,7 +190,8 @@ impl UnifiedBoxRegistry { // core_required (6個) + 特殊型の一部を予約型として保護 CoreBoxId::from_name(name) .map(|id| { - id.is_core_required() || matches!(id, CoreBoxId::Result | CoreBoxId::Method) + id.is_core_required() + || matches!(id, CoreBoxId::Result | CoreBoxId::Method) }) .unwrap_or(false) } diff --git a/src/boxes/file/handle_box.rs b/src/boxes/file/handle_box.rs index 99e645e1..82be1343 100644 --- a/src/boxes/file/handle_box.rs +++ b/src/boxes/file/handle_box.rs @@ -29,7 +29,11 @@ macro_rules! ny_wrap_string { pub fn $name(&self) -> StringBox { match self.$inner() { Ok(result) => StringBox::new(result), - Err(e) => panic!("FileHandleBox.{}() failed: {}", stringify!($name).trim_start_matches("ny_"), e), + Err(e) => panic!( + "FileHandleBox.{}() failed: {}", + stringify!($name).trim_start_matches("ny_"), + e + ), } } }; @@ -40,7 +44,11 @@ macro_rules! ny_wrap_bool { pub fn $name(&self) -> BoolBox { match self.$inner() { Ok(result) => BoolBox::new(result), - Err(e) => panic!("FileHandleBox.{}() failed: {}", stringify!($name).trim_start_matches("ny_"), e), + Err(e) => panic!( + "FileHandleBox.{}() failed: {}", + stringify!($name).trim_start_matches("ny_"), + e + ), } } }; @@ -51,7 +59,11 @@ macro_rules! ny_wrap_integer { pub fn $name(&self) -> crate::box_trait::IntegerBox { match self.$inner() { Ok(result) => crate::box_trait::IntegerBox::new(result as i64), - Err(e) => panic!("FileHandleBox.{}() failed: {}", stringify!($name).trim_start_matches("ny_"), e), + Err(e) => panic!( + "FileHandleBox.{}() failed: {}", + stringify!($name).trim_start_matches("ny_"), + e + ), } } }; @@ -156,8 +168,8 @@ impl FileHandleBox { } // Get FileIo provider to check capabilities - let provider = provider_lock::get_filebox_provider() - .ok_or_else(provider_not_initialized)?; + let provider = + provider_lock::get_filebox_provider().ok_or_else(provider_not_initialized)?; // NoFs profile check (Fail-Fast) let caps = provider.caps(); @@ -171,8 +183,8 @@ impl FileHandleBox { // Create NEW independent Ring0FsFileIo instance for this handle // IMPORTANT: We must create a new instance, not clone the Arc // because Ring0FsFileIo has internal state (path field) - use crate::runtime::get_global_ring0; use crate::providers::ring1::file::ring0_fs_fileio::Ring0FsFileIo; + use crate::runtime::get_global_ring0; let ring0 = get_global_ring0(); @@ -281,11 +293,12 @@ impl FileHandleBox { /// /// Unified metadata access through FileIo trait. fn metadata_internal(&self) -> Result { - let io = self.io.as_ref() + let io = self + .io + .as_ref() .ok_or_else(|| "FileHandleBox is not open".to_string())?; - io.stat() - .map_err(|e| format!("Metadata failed: {}", e)) + io.stat().map_err(|e| format!("Metadata failed: {}", e)) } /// Get file size in bytes @@ -308,7 +321,9 @@ impl FileHandleBox { /// /// Uses FileIo::exists() for direct check. pub fn exists(&self) -> Result { - let io = self.io.as_ref() + let io = self + .io + .as_ref() .ok_or_else(|| "FileHandleBox is not open".to_string())?; Ok(io.exists()) @@ -395,7 +410,9 @@ impl NyashBox for FileHandleBox { fn to_string_box(&self) -> StringBox { StringBox::new(format!( "FileHandleBox(path={}, mode={}, open={})", - self.path, self.mode, self.is_open() + self.path, + self.mode, + self.is_open() )) } @@ -442,8 +459,8 @@ mod tests { /// Helper: Initialize FileBox provider for tests fn init_test_provider() { - use crate::runtime::ring0::{default_ring0, init_global_ring0}; use crate::providers::ring1::file::ring0_fs_fileio::Ring0FsFileIo; + use crate::runtime::ring0::{default_ring0, init_global_ring0}; use std::panic; use std::sync::Arc; @@ -720,7 +737,7 @@ mod tests { init_test_provider(); let path = "/tmp/phase111_append_test.txt"; - let _ = fs::remove_file(path); // cleanup + let _ = fs::remove_file(path); // cleanup // First write (truncate) let mut handle = FileHandleBox::new(); @@ -751,7 +768,7 @@ mod tests { // Write test file let mut handle = FileHandleBox::new(); handle.open(path, "w").unwrap(); - handle.write_all("hello").unwrap(); // 5 bytes + handle.write_all("hello").unwrap(); // 5 bytes handle.close().unwrap(); // Check size @@ -967,7 +984,9 @@ mod tests { // Test metadata_internal() via stat() handle.open(path, "r").unwrap(); - let stat = handle.metadata_internal().expect("metadata_internal should succeed"); + let stat = handle + .metadata_internal() + .expect("metadata_internal should succeed"); assert!(stat.is_file); assert!(!stat.is_dir); assert_eq!(stat.size, 5); diff --git a/src/boxes/file/mod.rs b/src/boxes/file/mod.rs index 3aa80e8d..5de46f2f 100644 --- a/src/boxes/file/mod.rs +++ b/src/boxes/file/mod.rs @@ -89,8 +89,8 @@ impl FileBox { // Create NEW independent provider instance for this FileBox // IMPORTANT: We must create a new instance, not clone the Arc, // because Ring0FsFileIo has internal state (path field) - use crate::runtime::get_global_ring0; use crate::providers::ring1::file::ring0_fs_fileio::Ring0FsFileIo; + use crate::runtime::get_global_ring0; let ring0 = get_global_ring0(); @@ -126,7 +126,8 @@ impl FileBox { } // Phase 108: UTF-8 conversion (text-oriented design) let text = String::from_utf8_lossy(buf).to_string(); - provider.write(&text) + provider + .write(&text) .map_err(|e| format!("Write failed: {:?}", e)) } else { Err(no_provider_available()) @@ -160,7 +161,10 @@ impl FileBox { Err(e) => Box::new(StringBox::new(format!("Error: {:?}", e))), } } else { - Box::new(StringBox::new(format!("Error: {}", no_provider_available()))) + Box::new(StringBox::new(format!( + "Error: {}", + no_provider_available() + ))) } } @@ -230,8 +234,8 @@ impl std::fmt::Display for FileBox { #[cfg(test)] mod tests { use super::*; - use crate::runtime::ring0::{default_ring0, GLOBAL_RING0}; use crate::providers::ring1::file::ring0_fs_fileio::Ring0FsFileIo; + use crate::runtime::ring0::{default_ring0, GLOBAL_RING0}; use std::fs; use std::io::Write; @@ -345,11 +349,7 @@ mod tests { let fb = FileBox::open(tmp_path).expect("open failed"); let exists_box = fb.exists(); - let exists = exists_box - .as_any() - .downcast_ref::() - .unwrap() - .value; + let exists = exists_box.as_any().downcast_ref::().unwrap().value; assert!(exists); diff --git a/src/boxes/file/provider.rs b/src/boxes/file/provider.rs index 604e4506..5e1ea5f4 100644 --- a/src/boxes/file/provider.rs +++ b/src/boxes/file/provider.rs @@ -60,7 +60,8 @@ impl FileCaps { return Err("Read not supported by FileBox provider".to_string()); } } - "w" | "a" => { // Phase 111: "a" added + "w" | "a" => { + // Phase 111: "a" added if !self.write { return Err("Write not supported by FileBox provider".to_string()); } diff --git a/src/config/env/joinir_dev.rs b/src/config/env/joinir_dev.rs index 933ce2a1..1ace6892 100644 --- a/src/config/env/joinir_dev.rs +++ b/src/config/env/joinir_dev.rs @@ -99,6 +99,24 @@ pub fn read_quoted_ifmerge_enabled() -> bool { env_bool("HAKO_JOINIR_READ_QUOTED_IFMERGE") } +/// NYASH_JOINIR_NORMALIZED_DEV_RUN=1 - Run JoinIR runner through Normalized roundtrip (dev only) +pub fn joinir_normalized_dev_run_enabled() -> bool { + // Feature gate at call sites still applies; this helper just centralizes the env read. + env_bool("NYASH_JOINIR_NORMALIZED_DEV_RUN") +} + +/// Unified switch for Normalized dev experiments (feature + env). +/// +/// - Requires `--features normalized_dev` +/// - Requires `NYASH_JOINIR_NORMALIZED_DEV_RUN=1` +pub fn normalized_dev_enabled() -> bool { + if cfg!(feature = "normalized_dev") { + joinir_normalized_dev_run_enabled() + } else { + false + } +} + /// Phase 82: NYASH_PHI_FALLBACK_DISABLED=1 - Disable if_phi fallback (dev mode) /// /// lifecycle.rs の infer_type_from_phi* callsite を封じて、 @@ -124,7 +142,7 @@ pub fn read_quoted_ifmerge_enabled() -> bool { /// Phase 84-5: if_phi.rs 削除後は常に true /// 環境変数は統計目的でのみ残す pub fn phi_fallback_disabled() -> bool { - true // Phase 84-5: Always disabled after if_phi.rs deletion + true // Phase 84-5: Always disabled after if_phi.rs deletion } /// Phase 84-5: 統計・メトリクス用(オプション) diff --git a/src/main.rs b/src/main.rs index b6a33983..02fd89c0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -71,7 +71,7 @@ fn main() { #[cfg(test)] mod tests { - + use nyash_rust::box_trait::{NyashBox, StringBox}; #[test] diff --git a/src/mir/builder.rs b/src/mir/builder.rs index c3b57fba..f3903d1a 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -30,10 +30,10 @@ mod exprs_peek; // peek expression mod exprs_qmark; // ?-propagate mod fields; // field access/assignment lowering split mod if_form; -mod lifecycle; -mod loop_frontend_binding; // Phase 50: Loop Frontend Binding (JoinIR variable mapping) mod joinir_id_remapper; // Phase 189: JoinIR ID remapping (ValueId/BlockId translation) mod joinir_inline_boundary_injector; // Phase 189: JoinInlineBoundary Copy instruction injector +mod lifecycle; +mod loop_frontend_binding; // Phase 50: Loop Frontend Binding (JoinIR variable mapping) pub(crate) mod loops; mod ops; mod phi; @@ -285,7 +285,7 @@ impl MirBuilder { method_tail_index_source_len: 0, current_region_stack: Vec::new(), - fn_body_ast: None, // Phase 200-C: Initialize to None + fn_body_ast: None, // Phase 200-C: Initialize to None reserved_value_ids: HashSet::new(), // Phase 201-A: Initialize to empty loop_header_stack: Vec::new(), diff --git a/src/mir/builder/calls/guard.rs b/src/mir/builder/calls/guard.rs index e71dcf30..64255dd1 100644 --- a/src/mir/builder/calls/guard.rs +++ b/src/mir/builder/calls/guard.rs @@ -144,9 +144,7 @@ impl<'a> CalleeGuardBox<'a> { // Previously this converted to Global("JsonParserBox.parse") which failed // because the function table expected "JsonParserBox.parse/1" with arity. if trace_enabled { - eprintln!( - "[static-runtime-guard] StaticCompiler trusting methodize:" - ); + eprintln!("[static-runtime-guard] StaticCompiler trusting methodize:"); eprintln!( " {}.{} receiver %{} - passing through as static box call", box_name, method, recv.0 diff --git a/src/mir/builder/calls/lowering.rs b/src/mir/builder/calls/lowering.rs index e4564849..39be289c 100644 --- a/src/mir/builder/calls/lowering.rs +++ b/src/mir/builder/calls/lowering.rs @@ -344,7 +344,11 @@ impl MirBuilder { body: Vec, ) -> Result<(), String> { // Phase 200-C: Store fn_body for capture analysis - eprintln!("[lower_static_method_as_function] Storing fn_body with {} nodes for '{}'", body.len(), func_name); + eprintln!( + "[lower_static_method_as_function] Storing fn_body with {} nodes for '{}'", + body.len(), + func_name + ); self.fn_body_ast = Some(body.clone()); // Step 1: Context準備 diff --git a/src/mir/builder/control_flow/joinir/merge/block_allocator.rs b/src/mir/builder/control_flow/joinir/merge/block_allocator.rs index a7e73cbf..6c57776c 100644 --- a/src/mir/builder/control_flow/joinir/merge/block_allocator.rs +++ b/src/mir/builder/control_flow/joinir/merge/block_allocator.rs @@ -5,9 +5,9 @@ //! //! Phase 4 Extraction: Separated from merge_joinir_mir_blocks (lines 159-194) -use crate::mir::MirModule; -use crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper; use super::super::trace; +use crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper; +use crate::mir::MirModule; /// Phase 1: Allocate new block IDs for ALL functions (Phase 189) /// @@ -25,10 +25,16 @@ pub(super) fn allocate_blocks( // This exit_block_id will be returned and used by instruction_rewriter and exit_phi_builder let exit_block_id = builder.block_gen.next(); - eprintln!("[cf_loop/joinir/block_allocator] Phase 177-3: Allocated exit_block_id = {:?}", exit_block_id); + eprintln!( + "[cf_loop/joinir/block_allocator] Phase 177-3: Allocated exit_block_id = {:?}", + exit_block_id + ); // Phase 195: Use unified trace - trace::trace().blocks("allocator", "Phase 189: Allocating block IDs for all functions"); + trace::trace().blocks( + "allocator", + "Phase 189: Allocating block IDs for all functions", + ); // DETERMINISM FIX: Sort functions by name to ensure consistent iteration order let mut functions: Vec<_> = mir_module.functions.iter().collect(); @@ -50,7 +56,10 @@ pub(super) fn allocate_blocks( // Phase 195: Use unified trace trace::trace().blocks( "allocator", - &format!("Block remap: {}:{:?} → {:?}", func_name, old_block_id, new_block_id), + &format!( + "Block remap: {}:{:?} → {:?}", + func_name, old_block_id, new_block_id + ), ); } diff --git a/src/mir/builder/control_flow/joinir/merge/contract_checks.rs b/src/mir/builder/control_flow/joinir/merge/contract_checks.rs index ae099543..489ab5ab 100644 --- a/src/mir/builder/control_flow/joinir/merge/contract_checks.rs +++ b/src/mir/builder/control_flow/joinir/merge/contract_checks.rs @@ -1,7 +1,7 @@ +use super::LoopHeaderPhiInfo; use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary; use crate::mir::join_ir::lowering::join_value_space::{LOCAL_MAX, PARAM_MAX, PARAM_MIN}; use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId}; -use super::LoopHeaderPhiInfo; #[cfg(debug_assertions)] pub(super) fn verify_loop_header_phis( @@ -99,10 +99,7 @@ pub(super) fn verify_exit_line( } #[cfg(debug_assertions)] -pub(super) fn verify_valueid_regions( - loop_info: &LoopHeaderPhiInfo, - boundary: &JoinInlineBoundary, -) { +pub(super) fn verify_valueid_regions(loop_info: &LoopHeaderPhiInfo, boundary: &JoinInlineBoundary) { fn region_name(id: ValueId) -> &'static str { if id.0 < PARAM_MIN { "PHI Reserved" diff --git a/src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs b/src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs index 3660eaf2..362672c5 100644 --- a/src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs +++ b/src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs @@ -81,21 +81,25 @@ impl ExitMetaCollector { debug: bool, ) -> Vec { let mut bindings = Vec::new(); - let verbose = debug || crate::config::env::joinir_dev_enabled(); + let dev_on = crate::config::env::joinir_dev_enabled(); + let verbose = debug || dev_on; + let strict = crate::config::env::joinir_strict_enabled() || dev_on; - if debug { + if verbose { eprintln!( - "[cf_loop/exit_line] ExitMetaCollector: Collecting {} exit values", + "[joinir/exit-line] ExitMetaCollector: Collecting {} exit values", exit_meta.exit_values.len() ); } // Iterate over ExitMeta entries and build bindings for (carrier_name, join_exit_value) in &exit_meta.exit_values { - eprintln!( - "[cf_loop/exit_line] ExitMetaCollector DEBUG: Checking carrier '{}' in variable_map", - carrier_name - ); + if verbose { + eprintln!( + "[joinir/exit-line] checking carrier '{}' in variable_map", + carrier_name + ); + } // Look up host slot from variable_map if let Some(&host_slot) = builder.variable_map.get(carrier_name) { @@ -103,7 +107,8 @@ impl ExitMetaCollector { // Phase 228-8: Look up role from carrier_info if available let role = if let Some(ci) = carrier_info { - ci.carriers.iter() + ci.carriers + .iter() .find(|c| c.name == *carrier_name) .map(|c| c.role) .unwrap_or(CarrierRole::LoopState) @@ -118,18 +123,21 @@ impl ExitMetaCollector { role, }; - eprintln!( - "[cf_loop/exit_line] ExitMetaCollector: Collected '{}' JoinIR {:?} → HOST {:?}, role={:?}", - carrier_name, join_exit_value, host_slot, role - ); + if verbose { + eprintln!( + "[joinir/exit-line] collected '{}' JoinIR {:?} → HOST {:?}, role={:?}", + carrier_name, join_exit_value, host_slot, role + ); + } bindings.push(binding); } else { // Phase 228-8: Check if this is a ConditionOnly carrier // Phase 247-EX: Also check if this is a FromHost carrier (e.g., digit_value) - use crate::mir::join_ir::lowering::carrier_info::{CarrierRole, CarrierInit}; + use crate::mir::join_ir::lowering::carrier_info::{CarrierInit, CarrierRole}; let carrier_meta = if let Some(ci) = carrier_info { - ci.carriers.iter() + ci.carriers + .iter() .find(|c| c.name == *carrier_name) .map(|c| (c.role, c.init)) } else { @@ -147,10 +155,12 @@ impl ExitMetaCollector { role: CarrierRole::ConditionOnly, }; - eprintln!( - "[cf_loop/exit_line] Phase 228-8: Collected ConditionOnly carrier '{}' JoinIR {:?} (not in variable_map)", - carrier_name, join_exit_value - ); + if verbose { + eprintln!( + "[joinir/exit-line] collected ConditionOnly carrier '{}' JoinIR {:?} (not in variable_map)", + carrier_name, join_exit_value + ); + } bindings.push(binding); } @@ -164,10 +174,12 @@ impl ExitMetaCollector { role: CarrierRole::LoopState, }; - eprintln!( - "[cf_loop/exit_line] Phase 247-EX: Collected FromHost carrier '{}' JoinIR {:?} (not in variable_map)", - carrier_name, join_exit_value - ); + if verbose { + eprintln!( + "[joinir/exit-line] collected FromHost carrier '{}' JoinIR {:?} (not in variable_map)", + carrier_name, join_exit_value + ); + } bindings.push(binding); } @@ -180,18 +192,25 @@ impl ExitMetaCollector { role: CarrierRole::LoopState, }; - eprintln!( - "[cf_loop/exit_line] Phase 247-EX: Collected loop-local carrier '{}' JoinIR {:?} (no host slot)", - carrier_name, join_exit_value - ); + if verbose { + eprintln!( + "[joinir/exit-line] collected loop-local carrier '{}' JoinIR {:?} (no host slot)", + carrier_name, join_exit_value + ); + } bindings.push(binding); } _ => { - eprintln!( - "[cf_loop/exit_line] ExitMetaCollector DEBUG: Carrier '{}' not in variable_map and not ConditionOnly/FromHost (skip)", + let msg = format!( + "[joinir/exit-line] carrier '{}' not in variable_map and not ConditionOnly/FromHost (skip)", carrier_name ); + if strict { + panic!("{}", msg); + } else if verbose { + eprintln!("{}", msg); + } } } } @@ -199,7 +218,7 @@ impl ExitMetaCollector { if verbose { eprintln!( - "[cf_loop/exit_line] ExitMetaCollector: Collected {} bindings: {:?}", + "[joinir/exit-line] ExitMetaCollector: collected {} bindings: {:?}", bindings.len(), bindings ); @@ -211,7 +230,6 @@ impl ExitMetaCollector { #[cfg(test)] mod tests { - #[test] fn test_empty_exit_meta() { diff --git a/src/mir/builder/control_flow/joinir/merge/exit_line/mod.rs b/src/mir/builder/control_flow/joinir/merge/exit_line/mod.rs index 2e5fee76..0010d00a 100644 --- a/src/mir/builder/control_flow/joinir/merge/exit_line/mod.rs +++ b/src/mir/builder/control_flow/joinir/merge/exit_line/mod.rs @@ -48,14 +48,14 @@ //! ``` //! No changes to exit_line module needed! -pub mod reconnector; pub mod meta_collector; +pub mod reconnector; -pub use reconnector::ExitLineReconnector; pub use meta_collector::ExitMetaCollector; +pub use reconnector::ExitLineReconnector; -use std::collections::BTreeMap; use crate::mir::ValueId; +use std::collections::BTreeMap; /// Phase 33-10-Refactor-P2: ExitLineOrchestrator facade /// @@ -84,9 +84,10 @@ impl ExitLineOrchestrator { carrier_phis: &BTreeMap, debug: bool, ) -> Result<(), String> { - if debug { + let verbose = debug || crate::config::env::joinir_dev_enabled(); + if verbose { eprintln!( - "[cf_loop/joinir/exit_line] ExitLineOrchestrator: Starting Phase 6 reconnection with {} carrier PHIs", + "[joinir/exit-line] orchestrator start: {} carrier PHIs", carrier_phis.len() ); } @@ -94,8 +95,8 @@ impl ExitLineOrchestrator { // Phase 33-13: Delegate to ExitLineReconnector with carrier_phis ExitLineReconnector::reconnect(builder, boundary, carrier_phis, debug)?; - if debug { - eprintln!("[cf_loop/joinir/exit_line] ExitLineOrchestrator: Phase 6 complete"); + if verbose { + eprintln!("[joinir/exit-line] orchestrator complete"); } Ok(()) diff --git a/src/mir/builder/control_flow/joinir/merge/exit_line/reconnector.rs b/src/mir/builder/control_flow/joinir/merge/exit_line/reconnector.rs index bfe48a85..bdb43aab 100644 --- a/src/mir/builder/control_flow/joinir/merge/exit_line/reconnector.rs +++ b/src/mir/builder/control_flow/joinir/merge/exit_line/reconnector.rs @@ -79,26 +79,39 @@ impl ExitLineReconnector { carrier_phis: &BTreeMap, debug: bool, ) -> Result<(), String> { - let strict = crate::config::env::joinir_strict_enabled(); + let dev_on = crate::config::env::joinir_dev_enabled(); + let strict = crate::config::env::joinir_strict_enabled() || dev_on; + let verbose = debug || dev_on; - // Phase 177-STRUCT: Always log for debugging - eprintln!( - "[DEBUG-177/reconnect] ExitLineReconnector: {} exit bindings, {} carrier PHIs", - boundary.exit_bindings.len(), - carrier_phis.len() - ); + if verbose { + eprintln!( + "[joinir/exit-line] reconnect: {} exit bindings, {} carrier PHIs", + boundary.exit_bindings.len(), + carrier_phis.len() + ); + if !boundary.exit_bindings.is_empty() { + eprintln!( + "[joinir/exit-line] bindings {:?}", + boundary + .exit_bindings + .iter() + .map(|b| (&b.carrier_name, b.role, b.join_exit_value)) + .collect::>() + ); + } + } // Early return for empty exit_bindings if boundary.exit_bindings.is_empty() { - if debug { - eprintln!("[cf_loop/joinir/exit_line] ExitLineReconnector: No exit bindings, skipping reconnect"); + if verbose { + eprintln!("[joinir/exit-line] reconnect: no exit bindings, skip"); } return Ok(()); } - if debug { + if verbose { eprintln!( - "[cf_loop/joinir/exit_line] ExitLineReconnector: Reconnecting {} exit bindings with {} carrier PHIs", + "[joinir/exit-line] reconnecting {} exit bindings with {} carrier PHIs", boundary.exit_bindings.len(), carrier_phis.len() ); @@ -109,9 +122,9 @@ impl ExitLineReconnector { // Phase 228-8: Skip ConditionOnly carriers (no variable_map update needed) use crate::mir::join_ir::lowering::carrier_info::CarrierRole; if binding.role == CarrierRole::ConditionOnly { - if debug { + if verbose { eprintln!( - "[cf_loop/joinir/exit_line] Phase 228-8: Skipping ConditionOnly carrier '{}' (no variable_map update)", + "[joinir/exit-line] skip ConditionOnly carrier '{}' (no variable_map update)", binding.carrier_name ); } @@ -121,9 +134,9 @@ impl ExitLineReconnector { // Phase 33-13: Look up the PHI dst for this carrier let phi_dst = carrier_phis.get(&binding.carrier_name); - if debug { + if verbose { eprintln!( - "[cf_loop/joinir/exit_line] ExitLineReconnector: Carrier '{}' → phi_dst={:?}", + "[joinir/exit-line] carrier '{}' → phi_dst={:?}", binding.carrier_name, phi_dst ); } @@ -132,33 +145,34 @@ impl ExitLineReconnector { if let Some(&phi_value) = phi_dst { if let Some(var_vid) = builder.variable_map.get_mut(&binding.carrier_name) { // Phase 177-STRUCT: Always log for debugging - eprintln!( - "[DEBUG-177/reconnect] Updated variable_map['{}'] {:?} → {:?}", - binding.carrier_name, *var_vid, phi_value - ); + if verbose { + eprintln!( + "[joinir/exit-line] variable_map['{}'] {:?} → {:?}", + binding.carrier_name, *var_vid, phi_value + ); + } *var_vid = phi_value; - } else if debug { + } else if verbose { eprintln!( - "[cf_loop/joinir/exit_line] ExitLineReconnector WARNING: Carrier '{}' not found in variable_map", + "[joinir/exit-line] warning: carrier '{}' not found in variable_map", binding.carrier_name ); } else if strict { return Err(format!( - "[pattern2/exit_line] Missing variable_map entry for carrier '{}' (exit reconnection)", + "[joinir/exit-line] missing variable_map entry for carrier '{}' (exit reconnection)", binding.carrier_name )); } } else { if strict && binding.role != CarrierRole::ConditionOnly { return Err(format!( - "[pattern2/exit_line] Missing PHI dst for carrier '{}' ({} PHIs available)", + "[joinir/exit-line] missing PHI dst for carrier '{}' ({} PHIs available)", binding.carrier_name, carrier_phis.len() )); - } - if debug { + } else if verbose { eprintln!( - "[cf_loop/joinir/exit_line] ExitLineReconnector WARNING: No PHI dst for carrier '{}' (may be condition-only variable)", + "[joinir/exit-line] warning: No PHI dst for carrier '{}' (may be condition-only variable)", binding.carrier_name ); } @@ -169,7 +183,7 @@ impl ExitLineReconnector { #[allow(deprecated)] if !boundary.host_outputs.is_empty() && debug { eprintln!( - "[cf_loop/joinir/exit_line] WARNING: Using deprecated host_outputs. Migrate to exit_bindings." + "[joinir/exit-line] WARNING: Using deprecated host_outputs. Migrate to exit_bindings." ); } diff --git a/src/mir/builder/control_flow/joinir/merge/exit_phi_builder.rs b/src/mir/builder/control_flow/joinir/merge/exit_phi_builder.rs index 55b5776e..608344e5 100644 --- a/src/mir/builder/control_flow/joinir/merge/exit_phi_builder.rs +++ b/src/mir/builder/control_flow/joinir/merge/exit_phi_builder.rs @@ -85,7 +85,8 @@ pub(super) fn build_exit_phi( if debug { eprintln!( "[cf_loop/joinir] Created exit block: {:?} with {} carrier PHIs", - exit_block_id, carrier_phis.len() + exit_block_id, + carrier_phis.len() ); } phi_result diff --git a/src/mir/builder/control_flow/joinir/merge/expr_result_resolver.rs b/src/mir/builder/control_flow/joinir/merge/expr_result_resolver.rs index 21486eaa..82aa1a4a 100644 --- a/src/mir/builder/control_flow/joinir/merge/expr_result_resolver.rs +++ b/src/mir/builder/control_flow/joinir/merge/expr_result_resolver.rs @@ -12,10 +12,10 @@ //! 4. 非対応の場合 → remapper.get_value(expr_result) を返す //! 5. expr_result が None → None を返す -use std::collections::BTreeMap; -use crate::mir::ValueId; -use crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding; use crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper; +use crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding; +use crate::mir::ValueId; +use std::collections::BTreeMap; pub struct ExprResultResolver; @@ -60,7 +60,10 @@ impl ExprResultResolver { eprintln!( "[cf_loop/joinir] Phase 221: Resolving expr_result {:?}, exit_bindings={:?}", expr_result_id, - exit_bindings.iter().map(|b| (b.carrier_name.as_str(), b.join_exit_value)).collect::>() + exit_bindings + .iter() + .map(|b| (b.carrier_name.as_str(), b.join_exit_value)) + .collect::>() ); } @@ -114,13 +117,7 @@ mod tests { let carrier_phis = BTreeMap::new(); let remapper = JoinIrIdRemapper::new(); - let result = ExprResultResolver::resolve( - None, - &[], - &carrier_phis, - &remapper, - false, - ); + let result = ExprResultResolver::resolve(None, &[], &carrier_phis, &remapper, false); assert_eq!(result.unwrap(), None); } @@ -156,13 +153,8 @@ mod tests { let mut remapper = JoinIrIdRemapper::new(); remapper.set_value(ValueId(42), ValueId(200)); - let result = ExprResultResolver::resolve( - Some(ValueId(42)), - &[], - &carrier_phis, - &remapper, - false, - ); + let result = + ExprResultResolver::resolve(Some(ValueId(42)), &[], &carrier_phis, &remapper, false); assert_eq!(result.unwrap(), Some(ValueId(200))); } @@ -172,13 +164,8 @@ mod tests { let carrier_phis = BTreeMap::new(); let remapper = JoinIrIdRemapper::new(); - let result = ExprResultResolver::resolve( - Some(ValueId(999)), - &[], - &carrier_phis, - &remapper, - false, - ); + let result = + ExprResultResolver::resolve(Some(ValueId(999)), &[], &carrier_phis, &remapper, false); assert!(result.is_err()); assert!(result.unwrap_err().contains("not found in remapper")); diff --git a/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs b/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs index e7c964d7..b84d4c7c 100644 --- a/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs +++ b/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs @@ -61,7 +61,8 @@ pub(super) fn merge_and_rewrite( let boundary_input_set: std::collections::HashSet = boundary .map(|b| b.join_inputs.iter().copied().collect()) .unwrap_or_default(); - let strict_exit = crate::config::env::joinir_strict_enabled(); + let strict_exit = + crate::config::env::joinir_strict_enabled() || crate::config::env::joinir_dev_enabled(); // Phase 189-Fix: Collect return values from JoinIR functions for exit PHI let mut exit_phi_inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); @@ -614,7 +615,7 @@ pub(super) fn merge_and_rewrite( let expected_args = carrier_info.carriers.len() + 1; // loop_var + carriers if remapped_args.len() < expected_args { let msg = format!( - "[pattern2/exit_line] jump_args length mismatch: expected at least {} (loop_var + carriers) but got {} in block {:?}", + "[joinir/exit-line] jump_args length mismatch: expected at least {} (loop_var + carriers) but got {} in block {:?}", expected_args, remapped_args.len(), old_block.id @@ -673,7 +674,7 @@ pub(super) fn merge_and_rewrite( ); } else { let msg = format!( - "[pattern2/exit_line] Missing jump_args entry for carrier '{}' at index {} in block {:?}", + "[joinir/exit-line] Missing jump_args entry for carrier '{}' at index {} in block {:?}", carrier.name, jump_args_idx, old_block.id ); if strict_exit { diff --git a/src/mir/builder/control_flow/joinir/merge/loop_header_phi_builder.rs b/src/mir/builder/control_flow/joinir/merge/loop_header_phi_builder.rs index 06b2efd7..c20014be 100644 --- a/src/mir/builder/control_flow/joinir/merge/loop_header_phi_builder.rs +++ b/src/mir/builder/control_flow/joinir/merge/loop_header_phi_builder.rs @@ -27,8 +27,8 @@ //! Called from merge pipeline between Phase 3 (remap_values) and Phase 4 //! (instruction_rewriter). -use crate::mir::{MirInstruction, ValueId, BasicBlockId}; -use super::loop_header_phi_info::{LoopHeaderPhiInfo, CarrierPhiEntry}; +use super::loop_header_phi_info::{CarrierPhiEntry, LoopHeaderPhiInfo}; +use crate::mir::{BasicBlockId, MirInstruction, ValueId}; /// Builder for loop header PHIs /// @@ -64,7 +64,12 @@ impl LoopHeaderPhiBuilder { entry_block: BasicBlockId, loop_var_name: &str, loop_var_init: ValueId, - carriers: &[(String, ValueId, crate::mir::join_ir::lowering::carrier_info::CarrierInit, crate::mir::join_ir::lowering::carrier_info::CarrierRole)], // Phase 228: Added CarrierInit and CarrierRole + carriers: &[( + String, + ValueId, + crate::mir::join_ir::lowering::carrier_info::CarrierInit, + crate::mir::join_ir::lowering::carrier_info::CarrierRole, + )], // Phase 228: Added CarrierInit and CarrierRole expr_result_is_loop_var: bool, debug: bool, ) -> Result { @@ -188,6 +193,7 @@ impl LoopHeaderPhiBuilder { info: &LoopHeaderPhiInfo, debug: bool, ) -> Result<(), String> { + let dev_debug = debug || crate::config::env::joinir_dev_enabled(); if debug { eprintln!( "[cf_loop/joinir] Phase 33-16: Finalizing header PHIs at {:?}", @@ -206,16 +212,20 @@ impl LoopHeaderPhiBuilder { } // Get the header block from current function - let current_func = builder.current_function.as_mut().ok_or( - "Phase 33-16: No current function when finalizing header PHIs" - )?; + let current_func = builder + .current_function + .as_mut() + .ok_or("Phase 33-16: No current function when finalizing header PHIs")?; - let header_block = current_func.blocks.get_mut(&info.header_block).ok_or_else(|| { - format!( - "Phase 33-16: Header block {:?} not found in current function", - info.header_block - ) - })?; + let header_block = current_func + .blocks + .get_mut(&info.header_block) + .ok_or_else(|| { + format!( + "Phase 33-16: Header block {:?} not found in current function", + info.header_block + ) + })?; // Insert PHIs at the beginning of the header block (before other instructions) // Sorted by carrier name for determinism @@ -233,11 +243,11 @@ impl LoopHeaderPhiBuilder { phi_instructions.push(phi); - if debug { + if dev_debug { eprintln!( - "[cf_loop/joinir] Finalized carrier '{}' PHI: {:?} = phi [({:?}, {:?}), ({:?}, {:?})]", - name, entry.phi_dst, entry_block, entry_val, latch_block, latch_val - ); + "[joinir/header-phi] Finalized carrier '{}' PHI: {:?} = phi [({:?}, {:?}), ({:?}, {:?})]", + name, entry.phi_dst, entry_block, entry_val, latch_block, latch_val + ); } } @@ -253,9 +263,9 @@ impl LoopHeaderPhiBuilder { new_spans.append(&mut header_block.instruction_spans); header_block.instruction_spans = new_spans; - if debug { + if dev_debug { eprintln!( - "[cf_loop/joinir] Header block now has {} instructions", + "[joinir/header-phi] Header block now has {} instructions", header_block.instructions.len() ); } diff --git a/src/mir/builder/control_flow/joinir/merge/loop_header_phi_info.rs b/src/mir/builder/control_flow/joinir/merge/loop_header_phi_info.rs index a2cfc50e..54062805 100644 --- a/src/mir/builder/control_flow/joinir/merge/loop_header_phi_info.rs +++ b/src/mir/builder/control_flow/joinir/merge/loop_header_phi_info.rs @@ -10,8 +10,8 @@ //! - exit_phi_builder: to reference current loop values //! - ExitLineReconnector: to update variable_map with final values -use crate::mir::{BasicBlockId, ValueId}; use crate::mir::join_ir::lowering::carrier_info::CarrierRole; +use crate::mir::{BasicBlockId, ValueId}; use std::collections::BTreeMap; /// Information about loop header PHIs @@ -104,7 +104,9 @@ impl LoopHeaderPhiInfo { /// Check if all carriers have latch incoming set #[allow(dead_code)] pub fn all_latch_set(&self) -> bool { - self.carrier_phis.values().all(|e| e.latch_incoming.is_some()) + self.carrier_phis + .values() + .all(|e| e.latch_incoming.is_some()) } /// Phase 201-A: Get reserved ValueIds (PHI dsts that must not be overwritten) diff --git a/src/mir/builder/control_flow/joinir/merge/merge_result.rs b/src/mir/builder/control_flow/joinir/merge/merge_result.rs index 14e45d7a..d418f6d6 100644 --- a/src/mir/builder/control_flow/joinir/merge/merge_result.rs +++ b/src/mir/builder/control_flow/joinir/merge/merge_result.rs @@ -38,7 +38,12 @@ impl MergeResult { /// Add a carrier input #[allow(dead_code)] - pub fn add_carrier_input(&mut self, carrier_name: String, from_block: BasicBlockId, value: ValueId) { + pub fn add_carrier_input( + &mut self, + carrier_name: String, + from_block: BasicBlockId, + value: ValueId, + ) { self.carrier_inputs .entry(carrier_name) .or_insert_with(Vec::new) diff --git a/src/mir/builder/control_flow/joinir/merge/mod.rs b/src/mir/builder/control_flow/joinir/merge/mod.rs index 38cd6230..8ccabff6 100644 --- a/src/mir/builder/control_flow/joinir/merge/mod.rs +++ b/src/mir/builder/control_flow/joinir/merge/mod.rs @@ -13,24 +13,24 @@ //! Phase 4 Refactoring: Breaking down 714-line merge_joinir_mir_blocks() into focused modules mod block_allocator; -mod value_collector; -mod instruction_rewriter; -mod exit_phi_builder; -pub mod exit_line; -mod loop_header_phi_info; -mod loop_header_phi_builder; -mod tail_call_classifier; -mod merge_result; -mod expr_result_resolver; #[cfg(debug_assertions)] mod contract_checks; +pub mod exit_line; +mod exit_phi_builder; +mod expr_result_resolver; +mod instruction_rewriter; +mod loop_header_phi_builder; +mod loop_header_phi_info; +mod merge_result; +mod tail_call_classifier; +mod value_collector; // Phase 33-17: Re-export for use by other modules -pub use loop_header_phi_info::LoopHeaderPhiInfo; pub use loop_header_phi_builder::LoopHeaderPhiBuilder; +pub use loop_header_phi_info::LoopHeaderPhiInfo; -use crate::mir::{MirModule, ValueId}; use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary; +use crate::mir::{MirModule, ValueId}; /// Phase 49-3.2: Merge JoinIR-generated MIR blocks into current_function /// @@ -100,7 +100,12 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( let cond_summary: Vec = boundary .condition_bindings .iter() - .map(|b| format!("{}: host {:?} → join {:?}", b.name, b.host_value, b.join_value)) + .map(|b| { + format!( + "{}: host {:?} → join {:?}", + b.name, b.host_value, b.join_value + ) + }) .collect(); eprintln!( @@ -123,8 +128,7 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( let carriers: Vec = ci.carriers.iter().map(|c| c.name.clone()).collect(); eprintln!( "[cf_loop/joinir] Boundary carrier_info: loop_var='{}', carriers={:?}", - ci.loop_var_name, - carriers + ci.loop_var_name, carriers ); } } else { @@ -134,7 +138,8 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( // Phase 1: Allocate block IDs for all functions // Phase 177-3: block_allocator now returns exit_block_id to avoid conflicts - let (mut remapper, exit_block_id) = block_allocator::allocate_blocks(builder, mir_module, debug)?; + let (mut remapper, exit_block_id) = + block_allocator::allocate_blocks(builder, mir_module, debug)?; // Phase 2: Collect values from all functions let (mut used_values, value_to_func_name, function_params) = @@ -181,35 +186,55 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( .ok_or("JoinIR module has no functions (Phase 201-A)")?; let entry_block_remapped = remapper .get_block(entry_func_name, entry_func.entry_block) - .ok_or_else(|| format!("Entry block not found for {} (Phase 201-A)", entry_func_name))?; + .ok_or_else(|| { + format!( + "Entry block not found for {} (Phase 201-A)", + entry_func_name + ) + })?; // Get host's current block as the entry edge - let host_entry_block = builder.current_block.ok_or( - "Phase 201-A: No current block when building header PHIs" - )?; + let host_entry_block = builder + .current_block + .ok_or("Phase 201-A: No current block when building header PHIs")?; // Get loop variable's initial value from HOST - let loop_var_init = boundary.host_inputs.first().copied().ok_or( - "Phase 201-A: No host_inputs in boundary for loop_var_init" - )?; + let loop_var_init = boundary + .host_inputs + .first() + .copied() + .ok_or("Phase 201-A: No host_inputs in boundary for loop_var_init")?; // Phase 228-4: Extract carriers with their initialization strategy - let other_carriers: Vec<(String, ValueId, crate::mir::join_ir::lowering::carrier_info::CarrierInit, crate::mir::join_ir::lowering::carrier_info::CarrierRole)> = - if let Some(ref carrier_info) = boundary.carrier_info { - // Use carrier_info if available (Phase 228) - carrier_info.carriers - .iter() - .filter(|c| c.name != *loop_var_name) - .map(|c| (c.name.clone(), c.host_id, c.init, c.role)) - .collect() - } else { - // Fallback: exit_bindings から取得(既存動作) - boundary.exit_bindings - .iter() - .filter(|b| b.carrier_name != *loop_var_name) - .map(|b| (b.carrier_name.clone(), b.host_slot, crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState)) - .collect() - }; + let other_carriers: Vec<( + String, + ValueId, + crate::mir::join_ir::lowering::carrier_info::CarrierInit, + crate::mir::join_ir::lowering::carrier_info::CarrierRole, + )> = if let Some(ref carrier_info) = boundary.carrier_info { + // Use carrier_info if available (Phase 228) + carrier_info + .carriers + .iter() + .filter(|c| c.name != *loop_var_name) + .map(|c| (c.name.clone(), c.host_id, c.init, c.role)) + .collect() + } else { + // Fallback: exit_bindings から取得(既存動作) + boundary + .exit_bindings + .iter() + .filter(|b| b.carrier_name != *loop_var_name) + .map(|b| { + ( + b.carrier_name.clone(), + b.host_slot, + crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, + crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, + ) + }) + .collect() + }; if debug { eprintln!( @@ -219,7 +244,10 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( eprintln!( "[cf_loop/joinir] loop_var_init={:?}, carriers={:?}", loop_var_init, - other_carriers.iter().map(|(n, _, _, _)| n.as_str()).collect::>() + other_carriers + .iter() + .map(|(n, _, _, _)| n.as_str()) + .collect::>() ); } @@ -235,33 +263,53 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( debug, )? } else { - LoopHeaderPhiInfo::empty(remapper.get_block( - mir_module.functions.iter().next().unwrap().0, - mir_module.functions.iter().next().unwrap().1.entry_block - ).unwrap()) + LoopHeaderPhiInfo::empty( + remapper + .get_block( + mir_module.functions.iter().next().unwrap().0, + mir_module.functions.iter().next().unwrap().1.entry_block, + ) + .unwrap(), + ) } } else { - LoopHeaderPhiInfo::empty(remapper.get_block( - mir_module.functions.iter().next().unwrap().0, - mir_module.functions.iter().next().unwrap().1.entry_block - ).unwrap()) + LoopHeaderPhiInfo::empty( + remapper + .get_block( + mir_module.functions.iter().next().unwrap().0, + mir_module.functions.iter().next().unwrap().1.entry_block, + ) + .unwrap(), + ) }; // Phase 201-A: Get reserved PHI dst ValueIds and set in MirBuilder let reserved_phi_dsts = loop_header_phi_info.reserved_value_ids(); if debug && !reserved_phi_dsts.is_empty() { - eprintln!("[cf_loop/joinir] Phase 201-A: Reserved PHI dsts: {:?}", reserved_phi_dsts); + eprintln!( + "[cf_loop/joinir] Phase 201-A: Reserved PHI dsts: {:?}", + reserved_phi_dsts + ); } // Phase 201-A: Set reserved IDs in MirBuilder so next_value_id() skips them // This protects against carrier corruption when break conditions emit Const instructions builder.reserved_value_ids = reserved_phi_dsts.clone(); if debug && !builder.reserved_value_ids.is_empty() { - eprintln!("[cf_loop/joinir] Phase 201-A: Set builder.reserved_value_ids = {:?}", builder.reserved_value_ids); + eprintln!( + "[cf_loop/joinir] Phase 201-A: Set builder.reserved_value_ids = {:?}", + builder.reserved_value_ids + ); } // Phase 3: Remap ValueIds (with reserved PHI dsts protection) - remap_values(builder, &used_values, &mut remapper, &reserved_phi_dsts, debug)?; + remap_values( + builder, + &used_values, + &mut remapper, + &reserved_phi_dsts, + debug, + )?; // Phase 177-3 DEBUG: Verify remapper state after Phase 3 eprintln!("[DEBUG-177] === Remapper state after Phase 3 ==="); @@ -372,12 +420,18 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( eprintln!( "[DEBUG-177] Phase 33-21: carrier_phis count: {}, names: {:?}", loop_header_phi_info.carrier_phis.len(), - loop_header_phi_info.carrier_phis.iter().map(|(n, _)| n.as_str()).collect::>() + loop_header_phi_info + .carrier_phis + .iter() + .map(|(n, _)| n.as_str()) + .collect::>() ); // Map main's parameters to header PHI dsts // main params: [i_init, carrier1_init, ...] // carrier_phis: [("i", entry), ("sum", entry), ...] - for (idx, (carrier_name, entry)) in loop_header_phi_info.carrier_phis.iter().enumerate() { + for (idx, (carrier_name, entry)) in + loop_header_phi_info.carrier_phis.iter().enumerate() + { if let Some(&main_param) = main_params.get(idx) { // Phase 177-3: Don't override condition_bindings if condition_binding_ids.contains(&main_param) { @@ -401,7 +455,11 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( // They appear in condition_bindings (added by Phase 176-5) but need PHI remapping. for (carrier_name, entry) in &loop_header_phi_info.carrier_phis { // Check if this carrier has a condition_binding - if let Some(binding) = boundary.condition_bindings.iter().find(|cb| cb.name == *carrier_name) { + if let Some(binding) = boundary + .condition_bindings + .iter() + .find(|cb| cb.name == *carrier_name) + { // Skip if it's a true condition-only variable (already protected above) if condition_binding_ids.contains(&binding.join_value) { continue; @@ -451,8 +509,11 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( // Find which carrier this param belongs to by matching join_value // Check if this param was already handled by Phase 177-3-B let already_mapped = boundary.condition_bindings.iter().any(|cb| { - cb.join_value == *loop_step_param && - loop_header_phi_info.carrier_phis.iter().any(|(name, _)| name == &cb.name) + cb.join_value == *loop_step_param + && loop_header_phi_info + .carrier_phis + .iter() + .any(|(name, _)| name == &cb.name) }); if already_mapped { eprintln!( @@ -467,7 +528,9 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( // generates params in exit_bindings order. // // Solution: Use carrier_order (Vec) which preserves insertion order. - if let Some(param_idx) = loop_step_params.iter().position(|p| p == loop_step_param) { + if let Some(param_idx) = + loop_step_params.iter().position(|p| p == loop_step_param) + { // Map params[i] to carrier_order[i] if let (Some(carrier_name), Some(entry)) = ( loop_header_phi_info.get_carrier_at_index(param_idx), @@ -483,7 +546,9 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( } } - if function_params.get(main_func_name).is_none() && function_params.get(loop_step_func_name).is_none() { + if function_params.get(main_func_name).is_none() + && function_params.get(loop_step_func_name).is_none() + { // Fallback: Use old behavior (ValueId(0), ValueId(1), ...) // This handles patterns that don't have loop_step function if let Some(phi_dst) = loop_header_phi_info.get_carrier_phi(loop_var_name) { @@ -570,11 +635,7 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( loop_header_phi_info.carrier_phis.len() ); } - LoopHeaderPhiBuilder::finalize( - builder, - &loop_header_phi_info, - debug, - )?; + LoopHeaderPhiBuilder::finalize(builder, &loop_header_phi_info, debug)?; } // Phase 5: Build exit PHI (expr result only, not carrier PHIs) @@ -633,12 +694,18 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( let entry_block = loop_header_phi_info.header_block; if debug { - eprintln!("[cf_loop/joinir] Entry block (from loop_header_phi_info): {:?}", entry_block); + eprintln!( + "[cf_loop/joinir] Entry block (from loop_header_phi_info): {:?}", + entry_block + ); eprintln!( "[cf_loop/joinir] Current block before emit_jump: {:?}", builder.current_block ); - eprintln!("[cf_loop/joinir] Jumping to entry block: {:?}", entry_block); + eprintln!( + "[cf_loop/joinir] Jumping to entry block: {:?}", + entry_block + ); } crate::mir::builder::emission::branch::emit_jump(builder, entry_block)?; @@ -684,7 +751,10 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( // Future loops will set their own reserved IDs if !builder.reserved_value_ids.is_empty() { if debug { - eprintln!("[cf_loop/joinir] Phase 201-A: Clearing reserved_value_ids (was {:?})", builder.reserved_value_ids); + eprintln!( + "[cf_loop/joinir] Phase 201-A: Clearing reserved_value_ids (was {:?})", + builder.reserved_value_ids + ); } builder.reserved_value_ids.clear(); } @@ -698,7 +768,9 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( // Check if expr_result is the loop variable if let Some(loop_var_name) = &b.loop_var_name { // Find the exit binding for the loop variable - let loop_var_binding = b.exit_bindings.iter() + let loop_var_binding = b + .exit_bindings + .iter() .find(|binding| binding.carrier_name == *loop_var_name); if let Some(binding) = loop_var_binding { @@ -751,7 +823,10 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( // Return expr_result if present, otherwise fall back to exit_phi_result_id if let Some(resolved) = expr_result_value { if debug { - eprintln!("[cf_loop/joinir] Phase 246-EX-FIX: Returning expr_result_value {:?}", resolved); + eprintln!( + "[cf_loop/joinir] Phase 246-EX-FIX: Returning expr_result_value {:?}", + resolved + ); } Ok(Some(resolved)) } else { @@ -779,8 +854,11 @@ fn remap_values( debug: bool, ) -> Result<(), String> { if debug { - eprintln!("[cf_loop/joinir] Phase 3: Remapping {} ValueIds (reserved: {})", - used_values.len(), reserved_ids.len()); + eprintln!( + "[cf_loop/joinir] Phase 3: Remapping {} ValueIds (reserved: {})", + used_values.len(), + reserved_ids.len() + ); } for old_value in used_values { @@ -792,7 +870,10 @@ fn remap_values( } // Skip reserved ID - will try next one if debug { - eprintln!("[cf_loop/joinir] Phase 201-A: Skipping reserved PHI dst {:?}", candidate); + eprintln!( + "[cf_loop/joinir] Phase 201-A: Skipping reserved PHI dst {:?}", + candidate + ); } }; @@ -943,7 +1024,12 @@ fn verify_phi_inputs_defined( }); for instr in &header_block_data.instructions { - if let crate::mir::MirInstruction::Phi { dst, inputs, type_hint: _ } = instr { + if let crate::mir::MirInstruction::Phi { + dst, + inputs, + type_hint: _, + } = instr + { for (value_id, pred_block) in inputs { // Conservative sanity check: ValueId should not be suspiciously large // Phase 201 JoinValueSpace uses regions: diff --git a/src/mir/builder/control_flow/joinir/merge/tail_call_classifier.rs b/src/mir/builder/control_flow/joinir/merge/tail_call_classifier.rs index 602e7211..1a382ae9 100644 --- a/src/mir/builder/control_flow/joinir/merge/tail_call_classifier.rs +++ b/src/mir/builder/control_flow/joinir/merge/tail_call_classifier.rs @@ -68,9 +68,9 @@ mod tests { #[test] fn test_classify_loop_entry() { let result = classify_tail_call( - true, // is_entry_func_entry_block - true, // has_loop_header_phis - true, // has_boundary + true, // is_entry_func_entry_block + true, // has_loop_header_phis + true, // has_boundary ); assert_eq!(result, TailCallKind::LoopEntry); } diff --git a/src/mir/builder/control_flow/joinir/merge/value_collector.rs b/src/mir/builder/control_flow/joinir/merge/value_collector.rs index 8aff5071..35c2bb9d 100644 --- a/src/mir/builder/control_flow/joinir/merge/value_collector.rs +++ b/src/mir/builder/control_flow/joinir/merge/value_collector.rs @@ -5,10 +5,10 @@ //! //! Phase 4 Extraction: Separated from merge_joinir_mir_blocks (lines 202-246) -use crate::mir::{MirInstruction, MirModule, ValueId}; use crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper; -use std::collections::BTreeSet; // Phase 222.5-E: HashMap → BTreeMap for determinism -use std::collections::BTreeMap; // Phase 222.5-E: HashMap → BTreeMap for determinism +use crate::mir::{MirInstruction, MirModule, ValueId}; +use std::collections::BTreeMap; +use std::collections::BTreeSet; // Phase 222.5-E: HashMap → BTreeMap for determinism // Phase 222.5-E: HashMap → BTreeMap for determinism /// Phase 2: Collect all ValueIds used across ALL functions (Phase 189) /// @@ -22,15 +22,13 @@ pub(super) fn collect_values( ) -> Result< ( BTreeSet, - BTreeMap, // Phase 222.5-E: HashMap → BTreeMap for determinism - BTreeMap>, // Phase 222.5-E: HashMap → BTreeMap for determinism + BTreeMap, // Phase 222.5-E: HashMap → BTreeMap for determinism + BTreeMap>, // Phase 222.5-E: HashMap → BTreeMap for determinism ), String, > { if debug { - eprintln!( - "[cf_loop/joinir] Phase 189: Collecting value IDs from all functions" - ); + eprintln!("[cf_loop/joinir] Phase 189: Collecting value IDs from all functions"); } let mut used_values: BTreeSet = BTreeSet::new(); diff --git a/src/mir/builder/control_flow/joinir/mod.rs b/src/mir/builder/control_flow/joinir/mod.rs index ed40ec9e..3a4078a8 100644 --- a/src/mir/builder/control_flow/joinir/mod.rs +++ b/src/mir/builder/control_flow/joinir/mod.rs @@ -6,8 +6,8 @@ //! - MIR block merging (merge/) ✅ Phase 4 //! - Unified tracing (trace.rs) ✅ Phase 195 +pub(in crate::mir::builder) mod merge; pub(in crate::mir::builder) mod patterns; pub(in crate::mir::builder) mod routing; pub(in crate::mir::builder) mod routing_legacy_binding; -pub(in crate::mir::builder) mod merge; pub(in crate::mir::builder) mod trace; diff --git a/src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rs b/src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rs index 4ceeb092..5a5cdcdf 100644 --- a/src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rs +++ b/src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rs @@ -27,13 +27,13 @@ //! - **Testability**: Can test conversion independently //! - **Reduces duplication**: Eliminates 120 lines across Pattern 1-4 -use crate::mir::ValueId; use crate::mir::builder::MirBuilder; -use crate::mir::join_ir::JoinModule; use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary; +use crate::mir::join_ir::JoinModule; +use crate::mir::ValueId; use std::collections::BTreeMap; -pub struct JoinIRConversionPipeline; +pub(crate) struct JoinIRConversionPipeline; impl JoinIRConversionPipeline { /// Execute unified conversion pipeline @@ -82,41 +82,28 @@ impl JoinIRConversionPipeline { pattern_name: &str, debug: bool, ) -> Result, String> { - use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta; - use crate::mir::join_ir::frontend::JoinFuncMetaMap; use super::super::trace; + use crate::mir::join_ir::frontend::JoinFuncMetaMap; + use crate::mir::join_ir_vm_bridge::bridge_joinir_to_mir_with_meta; // Step 1: Log JoinIR stats (functions and blocks) trace::trace().joinir_stats( pattern_name, join_module.functions.len(), - join_module - .functions - .values() - .map(|f| f.body.len()) - .sum(), + join_module.functions.values().map(|f| f.body.len()).sum(), ); // Step 2: JoinModule → MirModule conversion // Pass empty meta map since minimal lowerers don't use metadata let empty_meta: JoinFuncMetaMap = BTreeMap::new(); - let mir_module = convert_join_module_to_mir_with_meta(&join_module, &empty_meta) - .map_err(|e| { - format!( - "[{}/pipeline] MIR conversion failed: {:?}", - pattern_name, e - ) - })?; + let mir_module = bridge_joinir_to_mir_with_meta(&join_module, &empty_meta) + .map_err(|e| format!("[{}/pipeline] MIR conversion failed: {:?}", pattern_name, e))?; // Step 3: Log MIR stats (functions and blocks) trace::trace().joinir_stats( pattern_name, mir_module.functions.len(), - mir_module - .functions - .values() - .map(|f| f.blocks.len()) - .sum(), + mir_module.functions.values().map(|f| f.blocks.len()).sum(), ); // Step 4: Merge into current function diff --git a/src/mir/builder/control_flow/joinir/patterns/exit_binding.rs b/src/mir/builder/control_flow/joinir/patterns/exit_binding.rs index 4899e16b..2f3e1de9 100644 --- a/src/mir/builder/control_flow/joinir/patterns/exit_binding.rs +++ b/src/mir/builder/control_flow/joinir/patterns/exit_binding.rs @@ -10,17 +10,17 @@ //! //! This orchestrator coordinates the three modules for a complete workflow. -use crate::mir::ValueId; -use crate::mir::join_ir::lowering::inline_boundary::{ - JoinInlineBoundary, LoopExitBinding, -}; use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta}; +use crate::mir::join_ir::lowering::inline_boundary::{JoinInlineBoundary, LoopExitBinding}; +use crate::mir::ValueId; use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism // Phase 222.5-C: Import modular components -use super::exit_binding_validator::validate_exit_binding; +use super::exit_binding_applicator::{ + apply_exit_bindings_to_boundary, create_loop_var_exit_binding, +}; use super::exit_binding_constructor::build_loop_exit_bindings; -use super::exit_binding_applicator::{apply_exit_bindings_to_boundary, create_loop_var_exit_binding}; +use super::exit_binding_validator::validate_exit_binding; /// Builder for generating loop exit bindings /// @@ -29,7 +29,7 @@ use super::exit_binding_applicator::{apply_exit_bindings_to_boundary, create_loo /// /// Eliminates hardcoded variable names and ValueId plumbing scattered across lowerers. #[allow(dead_code)] -pub struct ExitBindingBuilder<'a> { +pub(crate) struct ExitBindingBuilder<'a> { carrier_info: &'a CarrierInfo, exit_meta: &'a ExitMeta, variable_map: &'a mut BTreeMap, // Phase 222.5-D: HashMap → BTreeMap for determinism @@ -60,7 +60,7 @@ impl<'a> ExitBindingBuilder<'a> { /// /// ExitBindingBuilder instance, or error if metadata is inconsistent #[allow(dead_code)] - pub fn new( + pub(crate) fn new( carrier_info: &'a CarrierInfo, exit_meta: &'a ExitMeta, variable_map: &'a mut BTreeMap, // Phase 222.5-D: HashMap → BTreeMap for determinism @@ -86,7 +86,7 @@ impl<'a> ExitBindingBuilder<'a> { /// /// Vec of LoopExitBinding, one per carrier, sorted by carrier name #[allow(dead_code)] - pub fn build_loop_exit_bindings(&mut self) -> Result, String> { + pub(crate) fn build_loop_exit_bindings(&mut self) -> Result, String> { // Phase 222.5-C: Delegate to constructor module build_loop_exit_bindings(self.carrier_info, self.exit_meta, self.variable_map) } @@ -106,7 +106,7 @@ impl<'a> ExitBindingBuilder<'a> { /// /// Success or error if boundary cannot be updated #[allow(dead_code)] - pub fn apply_to_boundary(&self, boundary: &mut JoinInlineBoundary) -> Result<(), String> { + pub(crate) fn apply_to_boundary(&self, boundary: &mut JoinInlineBoundary) -> Result<(), String> { // Phase 222.5-C: Delegate to applicator module apply_exit_bindings_to_boundary( self.carrier_info, @@ -160,7 +160,9 @@ mod tests { let mut builder = ExitBindingBuilder::new(&carrier_info, &exit_meta, &mut variable_map) .expect("Failed to create builder"); - let bindings = builder.build_loop_exit_bindings().expect("Failed to build bindings"); + let bindings = builder + .build_loop_exit_bindings() + .expect("Failed to build bindings"); assert_eq!(bindings.len(), 1); assert_eq!(bindings[0].carrier_name, "sum"); @@ -213,7 +215,9 @@ mod tests { let mut builder = ExitBindingBuilder::new(&carrier_info, &exit_meta, &mut variable_map) .expect("Failed to create builder"); - let bindings = builder.build_loop_exit_bindings().expect("Failed to build bindings"); + let bindings = builder + .build_loop_exit_bindings() + .expect("Failed to build bindings"); assert_eq!(bindings.len(), 2); // Bindings should be sorted by carrier name @@ -345,24 +349,27 @@ mod tests { let mut builder = ExitBindingBuilder::new(&carrier_info, &exit_meta, &mut variable_map) .expect("Failed to create builder"); - let _ = builder.build_loop_exit_bindings().expect("Failed to build bindings"); + let _ = builder + .build_loop_exit_bindings() + .expect("Failed to build bindings"); let mut boundary = JoinInlineBoundary { host_inputs: vec![], join_inputs: vec![], - exit_bindings: vec![], // Phase 171: Add missing field + exit_bindings: vec![], // Phase 171: Add missing field #[allow(deprecated)] - host_outputs: vec![], // legacy, unused in new assertions + host_outputs: vec![], // legacy, unused in new assertions join_outputs: vec![], #[allow(deprecated)] - condition_inputs: vec![], // Phase 171: Add missing field - condition_bindings: vec![], // Phase 171-fix: Add missing field - expr_result: None, // Phase 33-14: Add missing field - loop_var_name: None, // Phase 33-16: Add missing field - carrier_info: None, // Phase 228: Add missing field + condition_inputs: vec![], // Phase 171: Add missing field + condition_bindings: vec![], // Phase 171-fix: Add missing field + expr_result: None, // Phase 33-14: Add missing field + loop_var_name: None, // Phase 33-16: Add missing field + carrier_info: None, // Phase 228: Add missing field }; - builder.apply_to_boundary(&mut boundary) + builder + .apply_to_boundary(&mut boundary) .expect("Failed to apply to boundary"); // Should have loop_var + sum carrier in exit_bindings diff --git a/src/mir/builder/control_flow/joinir/patterns/loop_scope_shape_builder.rs b/src/mir/builder/control_flow/joinir/patterns/loop_scope_shape_builder.rs index feb18602..37d729e7 100644 --- a/src/mir/builder/control_flow/joinir/patterns/loop_scope_shape_builder.rs +++ b/src/mir/builder/control_flow/joinir/patterns/loop_scope_shape_builder.rs @@ -38,7 +38,7 @@ use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; use crate::mir::BasicBlockId; use std::collections::{BTreeMap, BTreeSet}; -pub struct LoopScopeShapeBuilder; +pub(crate) struct LoopScopeShapeBuilder; impl LoopScopeShapeBuilder { /// Create LoopScopeShape with empty body_locals diff --git a/src/mir/builder/control_flow/joinir/patterns/mod.rs b/src/mir/builder/control_flow/joinir/patterns/mod.rs index 67eef90c..fe4b3a84 100644 --- a/src/mir/builder/control_flow/joinir/patterns/mod.rs +++ b/src/mir/builder/control_flow/joinir/patterns/mod.rs @@ -46,20 +46,20 @@ pub(in crate::mir::builder) mod common_init; pub(in crate::mir::builder) mod condition_env_builder; pub(in crate::mir::builder) mod conversion_pipeline; pub(in crate::mir::builder) mod exit_binding; -pub(in crate::mir::builder) mod exit_binding_validator; // Phase 222.5-C -pub(in crate::mir::builder) mod exit_binding_constructor; // Phase 222.5-C -pub(in crate::mir::builder) mod exit_binding_applicator; // Phase 222.5-C +pub(in crate::mir::builder) mod exit_binding_applicator; // Phase 222.5-C +pub(in crate::mir::builder) mod exit_binding_constructor; // Phase 222.5-C +pub(in crate::mir::builder) mod exit_binding_validator; // Phase 222.5-C pub(in crate::mir::builder) mod loop_scope_shape_builder; -pub(in crate::mir::builder) mod pattern_pipeline; pub(in crate::mir::builder) mod pattern1_minimal; pub(in crate::mir::builder) mod pattern2_with_break; pub(in crate::mir::builder) mod pattern3_with_if_phi; pub(in crate::mir::builder) mod pattern4_carrier_analyzer; pub(in crate::mir::builder) mod pattern4_with_continue; +pub(in crate::mir::builder) mod pattern_pipeline; pub(in crate::mir::builder) mod router; pub(in crate::mir::builder) mod trim_loop_lowering; // Phase 180: Dedicated Trim/P5 lowering module -pub(in crate::mir::builder) mod trim_pattern_validator; pub(in crate::mir::builder) mod trim_pattern_lowerer; +pub(in crate::mir::builder) mod trim_pattern_validator; // Re-export router for convenience pub(in crate::mir::builder) use router::{route_loop_pattern, LoopPatternContext}; diff --git a/src/mir/builder/control_flow/joinir/patterns/trim_pattern_lowerer.rs b/src/mir/builder/control_flow/joinir/patterns/trim_pattern_lowerer.rs index 9fdb352a..15d6fe0e 100644 --- a/src/mir/builder/control_flow/joinir/patterns/trim_pattern_lowerer.rs +++ b/src/mir/builder/control_flow/joinir/patterns/trim_pattern_lowerer.rs @@ -5,10 +5,10 @@ //! - Trim break condition generation //! - Carrier binding setup in ConditionEnv -use crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper; +use crate::ast::{ASTNode, Span, UnaryOperator}; use crate::mir::join_ir::lowering::condition_env::ConditionBinding; +use crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper; use crate::mir::ValueId; -use crate::ast::{ASTNode, UnaryOperator, Span}; pub(in crate::mir::builder::control_flow::joinir::patterns) struct TrimPatternLowerer; @@ -61,8 +61,12 @@ impl TrimPatternLowerer { let carrier_name = &trim_helper.carrier_name; // Get host ValueId for carrier - let host_value_id = get_host_value(carrier_name) - .ok_or_else(|| format!("[TrimPatternLowerer] Carrier '{}' not in variable_map", carrier_name))?; + let host_value_id = get_host_value(carrier_name).ok_or_else(|| { + format!( + "[TrimPatternLowerer] Carrier '{}' not in variable_map", + carrier_name + ) + })?; // Allocate JoinIR ValueId let joinir_value_id = alloc_join_value(); @@ -94,7 +98,8 @@ impl TrimPatternLowerer { insert_to_env: impl FnOnce(String, ValueId), alloc_join_value: &mut dyn FnMut() -> ValueId, ) -> Result { - let binding = Self::setup_trim_carrier_binding(trim_helper, get_host_value, alloc_join_value)?; + let binding = + Self::setup_trim_carrier_binding(trim_helper, get_host_value, alloc_join_value)?; // Insert into env insert_to_env(binding.name.clone(), binding.join_value); @@ -122,7 +127,9 @@ mod tests { // Should be UnaryOp::Not match result { - ASTNode::UnaryOp { operator, operand, .. } => { + ASTNode::UnaryOp { + operator, operand, .. + } => { assert_eq!(operator, UnaryOperator::Not); // Operand should be Variable with carrier name match *operand { @@ -151,11 +158,7 @@ mod tests { }; let get_value = |name: &str| variable_map.get(name).copied(); - let result = TrimPatternLowerer::setup_trim_carrier_binding( - &helper, - get_value, - &mut alloc, - ); + let result = TrimPatternLowerer::setup_trim_carrier_binding(&helper, get_value, &mut alloc); assert!(result.is_ok()); let binding = result.unwrap(); @@ -178,11 +181,7 @@ mod tests { }; let get_value = |name: &str| variable_map.get(name).copied(); - let result = TrimPatternLowerer::setup_trim_carrier_binding( - &helper, - get_value, - &mut alloc, - ); + let result = TrimPatternLowerer::setup_trim_carrier_binding(&helper, get_value, &mut alloc); assert!(result.is_err()); assert!(result.unwrap_err().contains("not in variable_map")); @@ -209,12 +208,8 @@ mod tests { env.insert(name, value); }; - let result = TrimPatternLowerer::add_to_condition_env( - &helper, - get_value, - insert, - &mut alloc, - ); + let result = + TrimPatternLowerer::add_to_condition_env(&helper, get_value, insert, &mut alloc); assert!(result.is_ok()); let binding = result.unwrap(); diff --git a/src/mir/builder/control_flow/joinir/patterns/trim_pattern_validator.rs b/src/mir/builder/control_flow/joinir/patterns/trim_pattern_validator.rs index 57a18f87..a2d2cad0 100644 --- a/src/mir/builder/control_flow/joinir/patterns/trim_pattern_validator.rs +++ b/src/mir/builder/control_flow/joinir/patterns/trim_pattern_validator.rs @@ -31,10 +31,10 @@ impl TrimPatternValidator { ch_value: ValueId, whitespace_chars: &[String], ) -> Result { - use crate::mir::builder::emission::constant::emit_string; use crate::mir::builder::emission::compare::emit_eq_to; - use crate::mir::types::BinaryOp; + use crate::mir::builder::emission::constant::emit_string; use crate::mir::instruction::MirInstruction; + use crate::mir::types::BinaryOp; if whitespace_chars.is_empty() { return Err("[emit_whitespace_check] Empty whitespace_chars".to_string()); @@ -89,18 +89,32 @@ impl TrimPatternValidator { ) -> Option<(String, Box)> { for stmt in loop_body { // Look for: local ch = ... - if let ASTNode::Local { variables, initial_values, .. } = stmt { + if let ASTNode::Local { + variables, + initial_values, + .. + } = stmt + { for (i, var) in variables.iter().enumerate() { if var == var_name { if let Some(Some(init_expr_box)) = initial_values.get(i) { // Check if it's a substring method call - if let ASTNode::MethodCall { object, method, arguments, .. } = init_expr_box.as_ref() { + if let ASTNode::MethodCall { + object, + method, + arguments, + .. + } = init_expr_box.as_ref() + { if method == "substring" && arguments.len() == 2 { // Extract object name if let ASTNode::Variable { name, .. } = object.as_ref() { // Return object name and start expression // (We assume second arg is start+1, first arg is start) - return Some((name.clone(), Box::new(arguments[0].clone()))); + return Some(( + name.clone(), + Box::new(arguments[0].clone()), + )); } } } @@ -116,45 +130,41 @@ impl TrimPatternValidator { #[cfg(test)] mod tests { use super::*; - use crate::ast::{LiteralValue, Span, BinaryOperator}; + use crate::ast::{BinaryOperator, LiteralValue, Span}; #[test] fn test_extract_substring_args_valid() { // Create: local ch = s.substring(start, start+1) - let body = vec![ - ASTNode::Local { - variables: vec!["ch".to_string()], - initial_values: vec![ - Some(Box::new(ASTNode::MethodCall { - object: Box::new(ASTNode::Variable { - name: "s".to_string(), + let body = vec![ASTNode::Local { + variables: vec!["ch".to_string()], + initial_values: vec![Some(Box::new(ASTNode::MethodCall { + object: Box::new(ASTNode::Variable { + name: "s".to_string(), + span: Span::unknown(), + }), + method: "substring".to_string(), + arguments: vec![ + ASTNode::Variable { + name: "start".to_string(), + span: Span::unknown(), + }, + ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(ASTNode::Variable { + name: "start".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(1), span: Span::unknown(), }), - method: "substring".to_string(), - arguments: vec![ - ASTNode::Variable { - name: "start".to_string(), - span: Span::unknown(), - }, - ASTNode::BinaryOp { - operator: BinaryOperator::Add, - left: Box::new(ASTNode::Variable { - name: "start".to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(1), - span: Span::unknown(), - }), - span: Span::unknown(), - }, - ], span: Span::unknown(), - })), + }, ], span: Span::unknown(), - }, - ]; + }))], + span: Span::unknown(), + }]; let result = TrimPatternValidator::extract_substring_args(&body, "ch"); assert!(result.is_some()); @@ -173,32 +183,28 @@ mod tests { #[test] fn test_extract_substring_args_wrong_var() { // local other_var = s.substring(0, 1) - let body = vec![ - ASTNode::Local { - variables: vec!["other_var".to_string()], - initial_values: vec![ - Some(Box::new(ASTNode::MethodCall { - object: Box::new(ASTNode::Variable { - name: "s".to_string(), - span: Span::unknown(), - }), - method: "substring".to_string(), - arguments: vec![ - ASTNode::Literal { - value: LiteralValue::Integer(0), - span: Span::unknown(), - }, - ASTNode::Literal { - value: LiteralValue::Integer(1), - span: Span::unknown(), - }, - ], + let body = vec![ASTNode::Local { + variables: vec!["other_var".to_string()], + initial_values: vec![Some(Box::new(ASTNode::MethodCall { + object: Box::new(ASTNode::Variable { + name: "s".to_string(), + span: Span::unknown(), + }), + method: "substring".to_string(), + arguments: vec![ + ASTNode::Literal { + value: LiteralValue::Integer(0), span: Span::unknown(), - })), + }, + ASTNode::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }, ], span: Span::unknown(), - }, - ]; + }))], + span: Span::unknown(), + }]; let result = TrimPatternValidator::extract_substring_args(&body, "ch"); assert!(result.is_none()); @@ -207,28 +213,22 @@ mod tests { #[test] fn test_extract_substring_args_wrong_method() { // local ch = s.charAt(0) - let body = vec![ - ASTNode::Local { - variables: vec!["ch".to_string()], - initial_values: vec![ - Some(Box::new(ASTNode::MethodCall { - object: Box::new(ASTNode::Variable { - name: "s".to_string(), - span: Span::unknown(), - }), - method: "charAt".to_string(), - arguments: vec![ - ASTNode::Literal { - value: LiteralValue::Integer(0), - span: Span::unknown(), - }, - ], - span: Span::unknown(), - })), - ], + let body = vec![ASTNode::Local { + variables: vec!["ch".to_string()], + initial_values: vec![Some(Box::new(ASTNode::MethodCall { + object: Box::new(ASTNode::Variable { + name: "s".to_string(), + span: Span::unknown(), + }), + method: "charAt".to_string(), + arguments: vec![ASTNode::Literal { + value: LiteralValue::Integer(0), + span: Span::unknown(), + }], span: Span::unknown(), - }, - ]; + }))], + span: Span::unknown(), + }]; let result = TrimPatternValidator::extract_substring_args(&body, "ch"); assert!(result.is_none()); diff --git a/src/mir/builder/control_flow/joinir/routing.rs b/src/mir/builder/control_flow/joinir/routing.rs index 7a2eed6a..eb3f62bd 100644 --- a/src/mir/builder/control_flow/joinir/routing.rs +++ b/src/mir/builder/control_flow/joinir/routing.rs @@ -1,9 +1,9 @@ //! JoinIR routing logic for loop lowering +use super::trace; use crate::ast::ASTNode; use crate::mir::builder::MirBuilder; use crate::mir::ValueId; -use super::trace; impl MirBuilder { /// Phase 49: Try JoinIR Frontend for mainline integration @@ -42,16 +42,17 @@ impl MirBuilder { // Phase 196: Default to structure-first routing now that LoopBuilder is removed. // - Default: ON (structure_only = true) to allow JoinIR patterns to run for all loops. // - To revert to the previous whitelist-only behavior, set NYASH_JOINIR_STRUCTURE_ONLY=0. - let structure_only = match std::env::var("NYASH_JOINIR_STRUCTURE_ONLY") - .ok() - .as_deref() - { + let structure_only = match std::env::var("NYASH_JOINIR_STRUCTURE_ONLY").ok().as_deref() { Some("0") | Some("off") => false, _ => true, }; if structure_only { - trace::trace().routing("router", &func_name, "Structure-only mode enabled, skipping whitelist"); + trace::trace().routing( + "router", + &func_name, + "Structure-only mode enabled, skipping whitelist", + ); } else { // Phase 49-4 + Phase 80: Multi-target routing (legacy whitelist) // - JoinIR は常時 ON。legacy LoopBuilder は削除済み。 @@ -60,25 +61,25 @@ impl MirBuilder { // Phase 188: Add "main" routing for loop pattern expansion // Phase 170: Add JsonParserBox methods for selfhost validation let is_target = match func_name.as_str() { - "main" => true, // Phase 188-Impl-1: Enable JoinIR for main function (Pattern 1) - "JoinIrMin.main/0" => true, // Phase 188-Impl-2: Enable JoinIR for JoinIrMin.main/0 (Pattern 2) + "main" => true, // Phase 188-Impl-1: Enable JoinIR for main function (Pattern 1) + "JoinIrMin.main/0" => true, // Phase 188-Impl-2: Enable JoinIR for JoinIrMin.main/0 (Pattern 2) "JsonTokenizer.print_tokens/0" => true, "ArrayExtBox.filter/2" => true, // Phase 170-A-1: Enable JsonParserBox methods for JoinIR routing "JsonParserBox._trim/1" => true, "JsonParserBox._skip_whitespace/2" => true, - "JsonParserBox._match_literal/3" => true, // Phase 182: Fixed arity (s, pos, literal) + "JsonParserBox._match_literal/3" => true, // Phase 182: Fixed arity (s, pos, literal) "JsonParserBox._parse_string/2" => true, "JsonParserBox._parse_array/2" => true, "JsonParserBox._parse_object/2" => true, // Phase 182: Add simple loop methods - "JsonParserBox._parse_number/2" => true, // P2 Break (s, pos) - "JsonParserBox._atoi/1" => true, // P2 Break (s) + "JsonParserBox._parse_number/2" => true, // P2 Break (s, pos) + "JsonParserBox._atoi/1" => true, // P2 Break (s) // Phase 170-A-1: Test methods (simplified versions) "TrimTest.trim/1" => true, - "Main.trim/1" => true, // Phase 171-fix: Main box variant - "Main.trim_string_simple/1" => true, // Phase 33-13: Simple trim variant - "TrimTest.main/0" => true, // Phase 170: TrimTest.main for loop pattern test + "Main.trim/1" => true, // Phase 171-fix: Main box variant + "Main.trim_string_simple/1" => true, // Phase 33-13: Simple trim variant + "TrimTest.main/0" => true, // Phase 170: TrimTest.main for loop pattern test // Phase 173: JsonParser P5 expansion test "JsonParserTest._skip_whitespace/3" => true, "JsonParserTest.main/0" => true, @@ -99,7 +100,11 @@ impl MirBuilder { // Debug log when routing through JoinIR Frontend // Phase 195: Check trace flags directly from JoinLoopTrace let debug = trace::trace().is_loopform_enabled() || trace::trace().is_mainline_enabled(); - trace::trace().routing("router", &func_name, "Routing through JoinIR Frontend mainline"); + trace::trace().routing( + "router", + &func_name, + "Routing through JoinIR Frontend mainline", + ); // Phase 49-3: Implement JoinIR Frontend integration self.cf_loop_joinir_impl(condition, body, &func_name, debug) @@ -123,9 +128,20 @@ impl MirBuilder { // Phase 200-C: Pass fn_body_ast to LoopPatternContext if available // Clone fn_body_ast to avoid borrow checker issues let fn_body_clone = self.fn_body_ast.clone(); - eprintln!("[routing] fn_body_ast is {} for '{}'", if fn_body_clone.is_some() { "SOME" } else { "NONE" }, func_name); + eprintln!( + "[routing] fn_body_ast is {} for '{}'", + if fn_body_clone.is_some() { + "SOME" + } else { + "NONE" + }, + func_name + ); let ctx = if let Some(ref fn_body) = fn_body_clone { - eprintln!("[routing] Creating ctx with fn_body ({} nodes)", fn_body.len()); + eprintln!( + "[routing] Creating ctx with fn_body ({} nodes)", + fn_body.len() + ); LoopPatternContext::with_fn_body(condition, body, &func_name, debug, fn_body) } else { LoopPatternContext::new(condition, body, &func_name, debug) @@ -137,7 +153,11 @@ impl MirBuilder { } // Phase 187-2: Pattern router failed, try legacy whitelist - trace::trace().routing("router", func_name, "Pattern router found no match, trying legacy whitelist"); + trace::trace().routing( + "router", + func_name, + "Pattern router found no match, trying legacy whitelist", + ); // Delegate to legacy binding path (routing_legacy_binding.rs) self.cf_loop_joinir_legacy_binding(condition, body, func_name, debug) diff --git a/src/mir/builder/control_flow/joinir/routing_legacy_binding.rs b/src/mir/builder/control_flow/joinir/routing_legacy_binding.rs index 48c976a1..b5da5bc7 100644 --- a/src/mir/builder/control_flow/joinir/routing_legacy_binding.rs +++ b/src/mir/builder/control_flow/joinir/routing_legacy_binding.rs @@ -7,10 +7,10 @@ //! Phase 194+ uses the pattern-based router instead. This legacy path is //! kept for backward compatibility with existing whitelist entries. +use super::trace; use crate::ast::ASTNode; use crate::mir::builder::MirBuilder; use crate::mir::ValueId; -use super::trace; impl MirBuilder { /// Phase 49-3: Legacy JoinIR Frontend integration via LoopFrontendBinding @@ -21,7 +21,7 @@ impl MirBuilder { /// # Pipeline /// 1. Build Loop AST → JSON v0 format (with "defs" array) /// 2. AstToJoinIrLowerer::lower_program_json() → JoinModule - /// 3. convert_join_module_to_mir_with_meta() → MirModule + /// 3. bridge_joinir_to_mir_with_meta() → MirModule /// 4. Merge MIR blocks into current_function pub(in crate::mir::builder) fn cf_loop_joinir_legacy_binding( &mut self, @@ -32,7 +32,7 @@ impl MirBuilder { ) -> Result, String> { use super::super::super::loop_frontend_binding::LoopFrontendBinding; use crate::mir::join_ir::frontend::{AstToJoinIrLowerer, JoinFuncMetaMap}; - use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta; + use crate::mir::join_ir_vm_bridge::bridge_joinir_to_mir_with_meta; use crate::mir::types::ConstValue; use crate::mir::MirInstruction; use crate::r#macro::ast_json::ast_to_json; @@ -42,7 +42,11 @@ impl MirBuilder { "JsonTokenizer.print_tokens/0" => LoopFrontendBinding::for_print_tokens(), "ArrayExtBox.filter/2" => LoopFrontendBinding::for_array_filter(), _ => { - trace::trace().routing("router", func_name, "No legacy binding defined, falling back"); + trace::trace().routing( + "router", + func_name, + "No legacy binding defined, falling back", + ); return Ok(None); } }; @@ -79,7 +83,10 @@ impl MirBuilder { if ext_ref == "me" || ext_ref.starts_with("me.") { continue; } - trace::trace().debug("router", &format!("Adding '{}' to params (external_ref)", ext_ref)); + trace::trace().debug( + "router", + &format!("Adding '{}' to params (external_ref)", ext_ref), + ); params.push(serde_json::json!(ext_ref)); } @@ -157,15 +164,11 @@ impl MirBuilder { trace::trace().joinir_stats( "router", join_module.functions.len(), - join_module - .functions - .values() - .map(|f| f.body.len()) - .sum(), + join_module.functions.values().map(|f| f.body.len()).sum(), ); // Step 4: Convert JoinModule to MIR - let mir_module = convert_join_module_to_mir_with_meta(&join_module, &join_meta) + let mir_module = bridge_joinir_to_mir_with_meta(&join_module, &join_meta) .map_err(|e| format!("JoinIR→MIR conversion failed: {}", e.message))?; // Debug MIR module if trace enabled @@ -187,7 +190,11 @@ impl MirBuilder { for (block_id, block) in &func.blocks { trace::trace().blocks( "router", - &format!("Block {:?}: {} instructions", block_id, block.instructions.len()), + &format!( + "Block {:?}: {} instructions", + block_id, + block.instructions.len() + ), ); for (i, inst) in block.instructions.iter().enumerate() { trace::trace().instructions("router", &format!("[{}] {:?}", i, inst)); diff --git a/src/mir/builder/control_flow/joinir/trace.rs b/src/mir/builder/control_flow/joinir/trace.rs index 632c0bce..54785d3a 100644 --- a/src/mir/builder/control_flow/joinir/trace.rs +++ b/src/mir/builder/control_flow/joinir/trace.rs @@ -41,8 +41,8 @@ //! NYASH_TRACE_VARMAP=1 NYASH_JOINIR_DEBUG=1 ./target/release/hakorune test.hako //! ``` -use std::collections::BTreeMap; use crate::mir::ValueId; +use std::collections::BTreeMap; /// Unified tracer for JoinIR loop operations. /// @@ -206,7 +206,10 @@ impl JoinLoopTrace { /// - `msg`: Human-readable message about the routing decision pub fn routing(&self, tag: &str, func_name: &str, msg: &str) { if self.joinir_enabled || self.mainline_enabled { - eprintln!("[trace:routing] {}: function '{}' - {}", tag, func_name, msg); + eprintln!( + "[trace:routing] {}: function '{}' - {}", + tag, func_name, msg + ); } } diff --git a/src/mir/builder/control_flow/utils.rs b/src/mir/builder/control_flow/utils.rs index 4954a75b..4fbf16f6 100644 --- a/src/mir/builder/control_flow/utils.rs +++ b/src/mir/builder/control_flow/utils.rs @@ -26,15 +26,14 @@ pub(in crate::mir::builder) fn extract_loop_variable_from_condition( condition: &ASTNode, ) -> Result { match condition { - ASTNode::BinaryOp { - operator, left, .. - } if matches!( - operator, - BinaryOperator::Less - | BinaryOperator::Greater - | BinaryOperator::LessEqual - | BinaryOperator::GreaterEqual - ) => + ASTNode::BinaryOp { operator, left, .. } + if matches!( + operator, + BinaryOperator::Less + | BinaryOperator::Greater + | BinaryOperator::LessEqual + | BinaryOperator::GreaterEqual + ) => { // Binary comparison: extract variable from left side match &**left { diff --git a/src/mir/builder/decls.rs b/src/mir/builder/decls.rs index b73af054..43e808f1 100644 --- a/src/mir/builder/decls.rs +++ b/src/mir/builder/decls.rs @@ -117,7 +117,10 @@ impl super::MirBuilder { } } // Phase 200-C: Store fn_body_ast for inline main() lowering - eprintln!("[build_static_main_box] Storing fn_body_ast with {} nodes for inline main()", body.len()); + eprintln!( + "[build_static_main_box] Storing fn_body_ast with {} nodes for inline main()", + body.len() + ); self.fn_body_ast = Some(body.clone()); // Lower statements in order to preserve def→use diff --git a/src/mir/builder/if_form.rs b/src/mir/builder/if_form.rs index 444f1e19..b31ed2b5 100644 --- a/src/mir/builder/if_form.rs +++ b/src/mir/builder/if_form.rs @@ -235,7 +235,8 @@ impl MirBuilder { crate::mir::join_ir::lowering::should_panic_on_joinir_failure(func_name, false); // Core ON + 本線対象の場合は環境変数に関わらず試行 - let should_try_joinir = core_mainline || (joinir_enabled && is_target && (joinir_toplevel || joinir_dryrun)); + let should_try_joinir = + core_mainline || (joinir_enabled && is_target && (joinir_toplevel || joinir_dryrun)); if should_try_joinir { if let Some(ref func) = self.current_function { diff --git a/src/mir/builder/joinir_id_remapper.rs b/src/mir/builder/joinir_id_remapper.rs index fce27387..c012e5a4 100644 --- a/src/mir/builder/joinir_id_remapper.rs +++ b/src/mir/builder/joinir_id_remapper.rs @@ -5,8 +5,8 @@ //! - JoinIR fragment → host MIR への ID変換 //! - 決定性を重視した実装 -use std::collections::BTreeMap; // Phase 222.5-E: HashMap → BTreeMap for determinism use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, ValueId}; +use std::collections::BTreeMap; // Phase 222.5-E: HashMap → BTreeMap for determinism /// JoinIR ID space を host MIR ID space に変換する pub struct JoinIrIdRemapper { @@ -29,7 +29,9 @@ impl JoinIrIdRemapper { /// Block ID mapping を取得 pub fn get_block(&self, func_name: &str, old_id: BasicBlockId) -> Option { - self.block_map.get(&(func_name.to_string(), old_id)).copied() + self.block_map + .get(&(func_name.to_string(), old_id)) + .copied() } /// Value ID mapping を取得 @@ -70,7 +72,9 @@ impl JoinIrIdRemapper { Compare { dst, lhs, rhs, .. } => vec![*dst, *lhs, *rhs], Load { dst, ptr } => vec![*dst, *ptr], Store { value, ptr } => vec![*value, *ptr], - Call { dst, func, args, .. } => { + Call { + dst, func, args, .. + } => { let mut vals = vec![*func]; if let Some(d) = dst { vals.push(*d); @@ -78,7 +82,9 @@ impl JoinIrIdRemapper { vals.extend(args.iter().copied()); vals } - BoxCall { dst, box_val, args, .. } => { + BoxCall { + dst, box_val, args, .. + } => { let mut vals = vec![*box_val]; if let Some(d) = dst { vals.push(*d); @@ -86,7 +92,9 @@ impl JoinIrIdRemapper { vals.extend(args.iter().copied()); vals } - PluginInvoke { dst, box_val, args, .. } => { + PluginInvoke { + dst, box_val, args, .. + } => { let mut vals = vec![*box_val]; if let Some(d) = dst { vals.push(*d); @@ -107,7 +115,9 @@ impl JoinIrIdRemapper { vals.extend(args.iter().copied()); vals } - NewClosure { dst, captures, me, .. } => { + NewClosure { + dst, captures, me, .. + } => { let mut vals = vec![*dst]; vals.extend(captures.iter().map(|(_, v)| *v)); if let Some(m) = me { @@ -119,10 +129,14 @@ impl JoinIrIdRemapper { Debug { value, .. } => vec![*value], DebugLog { values, .. } => values.clone(), Throw { exception, .. } => vec![*exception], - Catch { exception_value, .. } => vec![*exception_value], + Catch { + exception_value, .. + } => vec![*exception_value], RefNew { dst, box_val } => vec![*dst, *box_val], RefGet { dst, reference, .. } => vec![*dst, *reference], - RefSet { reference, value, .. } => vec![*reference, *value], + RefSet { + reference, value, .. + } => vec![*reference, *value], WeakNew { dst, box_val } => vec![*dst, *box_val], WeakLoad { dst, weak_ref } => vec![*dst, *weak_ref], WeakRef { dst, value, .. } => vec![*dst, *value], @@ -136,7 +150,11 @@ impl JoinIrIdRemapper { Cast { dst, value, .. } => vec![*dst, *value], TypeOp { dst, value, .. } => vec![*dst, *value], ArrayGet { dst, array, index } => vec![*dst, *array, *index], - ArraySet { array, index, value } => vec![*array, *index, *value], + ArraySet { + array, + index, + value, + } => vec![*array, *index, *value], Jump { .. } | Nop | Safepoint => vec![], ExternCall { dst, args, .. } => { let mut vals = Vec::new(); @@ -185,14 +203,27 @@ impl JoinIrIdRemapper { value: remap(*value), ptr: remap(*ptr), }, - Call { dst, func, callee, args, effects } => Call { + Call { + dst, + func, + callee, + args, + effects, + } => Call { dst: dst.map(remap), func: remap(*func), callee: callee.clone(), args: args.iter().map(|&a| remap(a)).collect(), effects: *effects, }, - BoxCall { dst, box_val, method, method_id, args, effects } => BoxCall { + BoxCall { + dst, + box_val, + method, + method_id, + args, + effects, + } => BoxCall { dst: dst.map(remap), box_val: remap(*box_val), method: method.clone(), @@ -200,7 +231,13 @@ impl JoinIrIdRemapper { args: args.iter().map(|&a| remap(a)).collect(), effects: *effects, }, - PluginInvoke { dst, box_val, method, args, effects } => PluginInvoke { + PluginInvoke { + dst, + box_val, + method, + args, + effects, + } => PluginInvoke { dst: dst.map(remap), box_val: remap(*box_val), method: method.clone(), @@ -211,16 +248,29 @@ impl JoinIrIdRemapper { dst: remap(*dst), src: remap(*src), }, - NewBox { dst, box_type, args } => NewBox { + NewBox { + dst, + box_type, + args, + } => NewBox { dst: remap(*dst), box_type: box_type.clone(), args: args.iter().map(|&a| remap(a)).collect(), }, - NewClosure { dst, params, body, captures, me } => NewClosure { + NewClosure { + dst, + params, + body, + captures, + me, + } => NewClosure { dst: remap(*dst), params: params.clone(), body: body.clone(), - captures: captures.iter().map(|(n, v)| (n.clone(), remap(*v))).collect(), + captures: captures + .iter() + .map(|(n, v)| (n.clone(), remap(*v))) + .collect(), me: me.map(remap), }, Print { value, effects } => Print { @@ -239,7 +289,11 @@ impl JoinIrIdRemapper { exception: remap(*exception), effects: *effects, }, - Catch { exception_type, exception_value, handler_bb } => Catch { + Catch { + exception_type, + exception_value, + handler_bb, + } => Catch { exception_type: exception_type.clone(), exception_value: remap(*exception_value), handler_bb: *handler_bb, @@ -248,12 +302,20 @@ impl JoinIrIdRemapper { dst: remap(*dst), box_val: remap(*box_val), }, - RefGet { dst, reference, field } => RefGet { + RefGet { + dst, + reference, + field, + } => RefGet { dst: remap(*dst), reference: remap(*reference), field: field.clone(), }, - RefSet { reference, field, value } => RefSet { + RefSet { + reference, + field, + value, + } => RefSet { reference: remap(*reference), field: field.clone(), value: remap(*value), @@ -271,12 +333,8 @@ impl JoinIrIdRemapper { op: *op, value: remap(*value), }, - BarrierRead { ptr } => BarrierRead { - ptr: remap(*ptr), - }, - BarrierWrite { ptr } => BarrierWrite { - ptr: remap(*ptr), - }, + BarrierRead { ptr } => BarrierRead { ptr: remap(*ptr) }, + BarrierWrite { ptr } => BarrierWrite { ptr: remap(*ptr) }, Barrier { op, ptr } => Barrier { op: *op, ptr: remap(*ptr), @@ -293,12 +351,20 @@ impl JoinIrIdRemapper { dst: remap(*dst), future: remap(*future), }, - TypeCheck { dst, value, expected_type } => TypeCheck { + TypeCheck { + dst, + value, + expected_type, + } => TypeCheck { dst: remap(*dst), value: remap(*value), expected_type: expected_type.clone(), }, - Cast { dst, value, target_type } => Cast { + Cast { + dst, + value, + target_type, + } => Cast { dst: remap(*dst), value: remap(*value), target_type: target_type.clone(), @@ -314,12 +380,22 @@ impl JoinIrIdRemapper { array: remap(*array), index: remap(*index), }, - ArraySet { array, index, value } => ArraySet { + ArraySet { + array, + index, + value, + } => ArraySet { array: remap(*array), index: remap(*index), value: remap(*value), }, - ExternCall { dst, iface_name, method_name, args, effects } => ExternCall { + ExternCall { + dst, + iface_name, + method_name, + args, + effects, + } => ExternCall { dst: dst.map(remap), iface_name: iface_name.clone(), method_name: method_name.clone(), @@ -327,12 +403,13 @@ impl JoinIrIdRemapper { effects: *effects, }, // Phase 189 FIX: Remap PHI dst and input values (BlockId remapping is done in control_flow.rs) - Phi { dst, inputs, type_hint } => Phi { + Phi { + dst, + inputs, + type_hint, + } => Phi { dst: remap(*dst), - inputs: inputs - .iter() - .map(|(bb, val)| (*bb, remap(*val))) - .collect(), + inputs: inputs.iter().map(|(bb, val)| (*bb, remap(*val))).collect(), type_hint: type_hint.clone(), }, // Pass through unchanged (Branch/Jump/Return handled separately) diff --git a/src/mir/builder/joinir_inline_boundary_injector.rs b/src/mir/builder/joinir_inline_boundary_injector.rs index 01903800..215cb867 100644 --- a/src/mir/builder/joinir_inline_boundary_injector.rs +++ b/src/mir/builder/joinir_inline_boundary_injector.rs @@ -5,9 +5,9 @@ //! - Entry block への Copy instruction 挿入 //! - SSA 値空間の接続 -use std::collections::BTreeMap; // Phase 222.5-E: HashMap → BTreeMap for determinism -use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId}; use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary; +use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId}; +use std::collections::BTreeMap; // Phase 222.5-E: HashMap → BTreeMap for determinism pub struct BoundaryInjector; @@ -56,10 +56,11 @@ impl BoundaryInjector { func: &mut MirFunction, entry_block_id: BasicBlockId, boundary: &JoinInlineBoundary, - value_map: &BTreeMap, // Phase 222.5-E: HashMap → BTreeMap for determinism + value_map: &BTreeMap, // Phase 222.5-E: HashMap → BTreeMap for determinism phi_dst_ids: &std::collections::HashSet, debug: bool, - ) -> Result, String> { // Phase 222.5-E: HashMap → BTreeMap for determinism + ) -> Result, String> { + // Phase 222.5-E: HashMap → BTreeMap for determinism // Phase 33-20: When loop_var_name is set, ALL join_inputs are handled by header PHIs // This includes the loop variable AND all other carriers from exit_bindings. // We skip ALL join_inputs Copy instructions, only condition_bindings remain. @@ -67,7 +68,7 @@ impl BoundaryInjector { // Phase 171-fix: Check both join_inputs and condition_bindings let effective_join_inputs = if skip_all_join_inputs { - 0 // Phase 33-20: All join_inputs are handled by header PHIs + 0 // Phase 33-20: All join_inputs are handled by header PHIs } else { boundary.join_inputs.len() }; @@ -104,7 +105,10 @@ impl BoundaryInjector { let mut reallocations = BTreeMap::new(); for binding in &boundary.condition_bindings { - let remapped_join = value_map.get(&binding.join_value).copied().unwrap_or(binding.join_value); + let remapped_join = value_map + .get(&binding.join_value) + .copied() + .unwrap_or(binding.join_value); if phi_dst_ids.contains(&remapped_join) { // Collision detected! Allocate a fresh ValueId @@ -132,29 +136,27 @@ impl BoundaryInjector { // Phase 171: Inject Copy instructions for join_inputs (loop parameters) // Phase 33-20: Skip ALL join_inputs when loop_var_name is set (header PHIs handle them) if !skip_all_join_inputs { - for (join_input, host_input) in boundary - .join_inputs - .iter() - .zip(boundary.host_inputs.iter()) + for (join_input, host_input) in + boundary.join_inputs.iter().zip(boundary.host_inputs.iter()) { - // リマップ後の ValueId を取得 - let remapped_join = value_map.get(join_input).copied().unwrap_or(*join_input); - let remapped_host = *host_input; // host_input is already in host space + // リマップ後の ValueId を取得 + let remapped_join = value_map.get(join_input).copied().unwrap_or(*join_input); + let remapped_host = *host_input; // host_input is already in host space - // Copy instruction: remapped_join = Copy remapped_host - let copy_inst = MirInstruction::Copy { - dst: remapped_join, - src: remapped_host, - }; + // Copy instruction: remapped_join = Copy remapped_host + let copy_inst = MirInstruction::Copy { + dst: remapped_join, + src: remapped_host, + }; - copy_instructions.push(copy_inst); + copy_instructions.push(copy_inst); - if debug { - eprintln!( - "[BoundaryInjector] Join input: Copy {:?} = Copy {:?}", - remapped_join, remapped_host - ); - } + if debug { + eprintln!( + "[BoundaryInjector] Join input: Copy {:?} = Copy {:?}", + remapped_join, remapped_host + ); + } } } @@ -178,10 +180,16 @@ impl BoundaryInjector { // Phase 177-3 Option B: Use pre-allocated reallocations for PHI collision cases for binding in &boundary.condition_bindings { // Look up the remapped JoinIR ValueId from value_map - let remapped_join = value_map.get(&binding.join_value).copied().unwrap_or(binding.join_value); + let remapped_join = value_map + .get(&binding.join_value) + .copied() + .unwrap_or(binding.join_value); // Phase 177-3 Option B: Check if this binding was reallocated (PHI collision case) - let final_dst = reallocations.get(&binding.join_value).copied().unwrap_or(remapped_join); + let final_dst = reallocations + .get(&binding.join_value) + .copied() + .unwrap_or(remapped_join); // Copy instruction: final_dst = Copy host_value let copy_inst = MirInstruction::Copy { @@ -203,7 +211,9 @@ impl BoundaryInjector { // Entry block の先頭に Copy instructions を挿入 // Reverse order to preserve original order when inserting at position 0 // Phase 189 FIX: Also insert corresponding spans - let default_span = entry_block.instruction_spans.first() + let default_span = entry_block + .instruction_spans + .first() .copied() .unwrap_or_else(crate::ast::Span::unknown); for inst in copy_instructions.into_iter().rev() { diff --git a/src/mir/builder/lifecycle.rs b/src/mir/builder/lifecycle.rs index 0371f3e6..9c931f44 100644 --- a/src/mir/builder/lifecycle.rs +++ b/src/mir/builder/lifecycle.rs @@ -341,9 +341,11 @@ impl super::MirBuilder { // P3-D は「既知メソッドの戻り値型」を直接推論する。 // BoxCall の method 名から TypeAnnotationBox と同じマッピングで型を取得。 if hint.is_none() { - if let Some(mt) = - MethodReturnHintBox::resolve_for_return(&function, *v, &self.value_types) - { + if let Some(mt) = MethodReturnHintBox::resolve_for_return( + &function, + *v, + &self.value_types, + ) { if std::env::var("NYASH_P3D_DEBUG").is_ok() { eprintln!( "[lifecycle/p3d] {} type inferred via MethodReturnHintBox: {:?}", diff --git a/src/mir/builder/loop_frontend_binding.rs b/src/mir/builder/loop_frontend_binding.rs index 0dc7ab35..d407b9b5 100644 --- a/src/mir/builder/loop_frontend_binding.rs +++ b/src/mir/builder/loop_frontend_binding.rs @@ -158,9 +158,9 @@ impl LoopFrontendBinding { Self { counter_var: "i".to_string(), counter_init: 0, - accumulator_var: None, // No accumulator + accumulator_var: None, // No accumulator bound_expr: BoundExpr::Constant(3), // Constant bound - external_refs: vec![], // No external refs + external_refs: vec![], // No external refs pattern: LoopPattern::Simple { has_accumulator: false, }, diff --git a/src/mir/builder/observe/types.rs b/src/mir/builder/observe/types.rs index d7b7e723..8ec3481c 100644 --- a/src/mir/builder/observe/types.rs +++ b/src/mir/builder/observe/types.rs @@ -16,18 +16,20 @@ fn enabled() -> bool { /// Trace when a newbox/class origin is registered. pub fn origin(event: &str, vid: ValueId, class: &str) { if enabled() { - get_global_ring0() - .log - .debug(&format!("[type-trace] origin:{} %{} ← {}", event, vid.0, class)); + get_global_ring0().log.debug(&format!( + "[type-trace] origin:{} %{} ← {}", + event, vid.0, class + )); } } /// Trace when a concrete MirType is recorded. pub fn ty(event: &str, vid: ValueId, ty: &MirType) { if enabled() { - get_global_ring0() - .log - .debug(&format!("[type-trace] type:{} %{} ← {:?}", event, vid.0, ty)); + get_global_ring0().log.debug(&format!( + "[type-trace] type:{} %{} ← {:?}", + event, vid.0, ty + )); } } diff --git a/src/mir/builder/type_registry.rs b/src/mir/builder/type_registry.rs index 2d17d855..df2c3a21 100644 --- a/src/mir/builder/type_registry.rs +++ b/src/mir/builder/type_registry.rs @@ -81,7 +81,7 @@ impl TypeRegistry { source: format!("param:{}:{:?}", param_name, ty), timestamp: self.trace_log.len(), }; - self.trace_log.push(entry.clone()); + self.trace_log.push(entry.clone()); get_global_ring0() .log .debug(&format!("[type-registry] {} {:?}", entry.source, vid)); @@ -217,9 +217,10 @@ impl TypeRegistry { // 最終フォールバック: UnknownBox if self.trace_enabled { - get_global_ring0() - .log - .warn(&format!("[type-registry] WARNING: UnknownBox for %{}", vid.0)); + get_global_ring0().log.warn(&format!( + "[type-registry] WARNING: UnknownBox for %{}", + vid.0 + )); } "UnknownBox".to_string() } diff --git a/src/mir/hints.rs b/src/mir/hints.rs index 3aed2dc2..b9784816 100644 --- a/src/mir/hints.rs +++ b/src/mir/hints.rs @@ -72,33 +72,23 @@ impl HintSink { match cfg.sink { HintSinkTarget::None => {} HintSinkTarget::Stderr => match hint { - HintKind::ScopeEnter(id) => { - get_global_ring0() - .log - .debug(&format!("[mir][hint] ScopeEnter({})", id)) - } - HintKind::ScopeLeave(id) => { - get_global_ring0() - .log - .debug(&format!("[mir][hint] ScopeLeave({})", id)) - } + HintKind::ScopeEnter(id) => get_global_ring0() + .log + .debug(&format!("[mir][hint] ScopeEnter({})", id)), + HintKind::ScopeLeave(id) => get_global_ring0() + .log + .debug(&format!("[mir][hint] ScopeLeave({})", id)), HintKind::Defer(calls) => get_global_ring0() .log .debug(&format!("[mir][hint] Defer({})", calls.join(";"))), HintKind::JoinResult(var) => get_global_ring0() .log .debug(&format!("[mir][hint] JoinResult({})", var)), - HintKind::LoopCarrier(vars) => { - get_global_ring0() - .log - .debug(&format!("[mir][hint] LoopCarrier({})", vars.join(","))) - } - HintKind::LoopHeader => { - get_global_ring0().log.debug("[mir][hint] LoopHeader") - } - HintKind::LoopLatch => { - get_global_ring0().log.debug("[mir][hint] LoopLatch") - } + HintKind::LoopCarrier(vars) => get_global_ring0() + .log + .debug(&format!("[mir][hint] LoopCarrier({})", vars.join(","))), + HintKind::LoopHeader => get_global_ring0().log.debug("[mir][hint] LoopHeader"), + HintKind::LoopLatch => get_global_ring0().log.debug("[mir][hint] LoopLatch"), HintKind::NoEmptyPhi => get_global_ring0().log.debug("[mir][hint] NoEmptyPhi"), }, HintSinkTarget::Jsonl(ref path) => { diff --git a/src/mir/instruction/methods.rs b/src/mir/instruction/methods.rs index 482dcc70..ce6767fc 100644 --- a/src/mir/instruction/methods.rs +++ b/src/mir/instruction/methods.rs @@ -150,12 +150,17 @@ impl MirInstruction { // Handle Call instructions here (not in inst_meta) because CallLikeInst // doesn't have the callee field needed for Callee::Method receiver handling. // This is the single source of truth for Call's used values. - if let MirInstruction::Call { callee, func, args, .. } = self { + if let MirInstruction::Call { + callee, func, args, .. + } = self + { use crate::mir::definitions::call_unified::Callee; let mut used: Vec = Vec::new(); match callee { // Unified path: Callee::Method with receiver - Some(Callee::Method { receiver: Some(r), .. }) => { + Some(Callee::Method { + receiver: Some(r), .. + }) => { used.push(*r); } // Legacy path: func ValueId is the callable diff --git a/src/mir/join_ir/frontend/ast_lowerer/if_return.rs b/src/mir/join_ir/frontend/ast_lowerer/if_return.rs index 24bec5a8..2a5bef13 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/if_return.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/if_return.rs @@ -20,6 +20,7 @@ //! - 単一関数: cond 評価 → Select(cond, then_val, else_val) → Ret use super::{AstToJoinIrLowerer, BTreeMap, ExtractCtx, JoinFunction, JoinInst, JoinModule}; +use crate::mir::join_ir::JoinIrPhase; impl AstToJoinIrLowerer { /// If Return pattern の共通 lowering @@ -152,6 +153,7 @@ impl AstToJoinIrLowerer { JoinModule { functions, entry: Some(func_id), + phase: JoinIrPhase::Structured, } } } diff --git a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/common.rs b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/common.rs index 3a45b066..cd168821 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/common.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/common.rs @@ -13,6 +13,7 @@ //! - `create_k_exit_function()`: k_exit 関数生成 use super::{AstToJoinIrLowerer, JoinModule}; +use crate::mir::join_ir::JoinIrPhase; use crate::mir::join_ir::{JoinFuncId, JoinFunction, JoinInst}; use crate::mir::ValueId; use std::collections::BTreeMap; @@ -403,5 +404,6 @@ pub fn build_join_module( JoinModule { functions, entry: Some(entry_id), + phase: JoinIrPhase::Structured, } } diff --git a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns_old.rs b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns_old.rs index 771ad0f6..057710b4 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns_old.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns_old.rs @@ -1,5 +1,6 @@ use super::BTreeMap; use super::{AstToJoinIrLowerer, ConstValue, ExtractCtx, JoinFunction, JoinInst, JoinModule}; +use crate::mir::join_ir::JoinIrPhase; impl AstToJoinIrLowerer { /// Phase 34-8: Break/Continue 付きループの lowering(パターン検出) @@ -435,6 +436,7 @@ impl AstToJoinIrLowerer { JoinModule { functions, entry: Some(entry_id), + phase: JoinIrPhase::Structured, } } @@ -642,6 +644,7 @@ impl AstToJoinIrLowerer { JoinModule { functions, entry: Some(entry_id), + phase: JoinIrPhase::Structured, } } @@ -889,6 +892,7 @@ impl AstToJoinIrLowerer { JoinModule { functions, entry: Some(entry_id), + phase: JoinIrPhase::Structured, } } } diff --git a/src/mir/join_ir/frontend/ast_lowerer/mod.rs b/src/mir/join_ir/frontend/ast_lowerer/mod.rs index 28d4ebaf..a6607e58 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/mod.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/mod.rs @@ -43,6 +43,56 @@ mod tests; pub(crate) use context::ExtractCtx; pub(crate) use stmt_handlers::StatementEffect; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum FunctionRoute { + IfReturn, + LoopFrontend, + NestedIf, + ReadQuoted, +} + +fn resolve_function_route(func_name: &str) -> Result { + const IF_RETURN_NAMES: &[&str] = &["test", "local", "_read_value_from_pair"]; + const LOOP_NAMES: &[&str] = &["simple", "filter", "print_tokens", "map", "reduce", "fold", "jsonparser_skip_ws_mini"]; + + if IF_RETURN_NAMES.contains(&func_name) { + return Ok(FunctionRoute::IfReturn); + } + + if LOOP_NAMES.contains(&func_name) { + return Ok(FunctionRoute::LoopFrontend); + } + + if func_name == "parse_loop" { + if crate::config::env::joinir_dev_enabled() + && std::env::var("HAKO_JOINIR_NESTED_IF").ok().as_deref() == Some("1") + { + return Ok(FunctionRoute::NestedIf); + } + return Err( + "[joinir/frontend] 'parse_loop' requires HAKO_JOINIR_NESTED_IF=1 (dev only)" + .to_string(), + ); + } + + if func_name == "read_quoted_from" { + if crate::config::env::joinir_dev_enabled() + && std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() == Some("1") + { + return Ok(FunctionRoute::ReadQuoted); + } + return Err( + "[joinir/frontend] 'read_quoted_from' requires HAKO_JOINIR_READ_QUOTED=1 (dev only)" + .to_string(), + ); + } + + Err(format!( + "[joinir/frontend] unsupported function '{}' (dev fixture not registered)", + func_name + )) +} + /// AST/CFG → JoinIR 変換器 /// /// Phase 34-2: Program(JSON v0) から tiny IfSelect ケースを JoinIR に変換 @@ -85,51 +135,17 @@ impl AstToJoinIrLowerer { .as_str() .expect("Function must have 'name'"); - // 3. 関数名で分岐(Phase P3: LoopFrontendBinding 層導入) - // - // パターン分類: - // - If Return pattern: test/local/_read_value_from_pair - // - Loop pattern: simple 等 → LoopFrontendBinding 経由 - // - NestedIfMerge pattern: parse_loop (dev flag gated) - // - ReadQuoted pattern: read_quoted_from (dev flag gated) - match func_name { - "test" | "local" | "_read_value_from_pair" => { - self.lower_if_return_pattern(program_json) - } - "simple" | "filter" | "print_tokens" | "map" | "reduce" | "fold" => { - // Phase P3: LoopFrontendBinding 層経由でディスパッチ - loop_frontend_binding::lower_loop_by_function_name(self, program_json) - } - "parse_loop" => { - // Phase 41-4: NestedIfMerge pattern (dev flag gated) - // Guard: JoinIR dev + HAKO_JOINIR_NESTED_IF=1 を要求 - if crate::config::env::joinir_dev_enabled() - && std::env::var("HAKO_JOINIR_NESTED_IF").ok().as_deref() == Some("1") - { - self.lower_nested_if_pattern(program_json) - } else { - // Dev flag が OFF の場合は panic(旧ルートにフォールバックするため) - panic!( - "parse_loop NestedIfMerge requires HAKO_JOINIR_NESTED_IF=1. \ - Set env var to enable Phase 41-4 route." - ); - } - } - "read_quoted_from" => { - // Phase 45: read_quoted_from pattern (dev flag gated) - // Guard if + Loop with break + accumulator - if crate::config::env::joinir_dev_enabled() - && std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() == Some("1") - { - self.lower_read_quoted_pattern(program_json) - } else { - panic!( - "read_quoted_from JoinIR requires HAKO_JOINIR_READ_QUOTED=1. \ - Set env var to enable Phase 45 route." - ); - } - } - _ => panic!("Unsupported function: {}", func_name), + let route = resolve_function_route(func_name) + .unwrap_or_else(|msg| panic!("{msg}")); + + match route { + FunctionRoute::IfReturn => self.lower_if_return_pattern(program_json), + FunctionRoute::LoopFrontend => loop_frontend_binding::lower_loop_by_function_name( + self, + program_json, + ), + FunctionRoute::NestedIf => self.lower_nested_if_pattern(program_json), + FunctionRoute::ReadQuoted => self.lower_read_quoted_pattern(program_json), } } diff --git a/src/mir/join_ir/frontend/ast_lowerer/nested_if.rs b/src/mir/join_ir/frontend/ast_lowerer/nested_if.rs index b48c8f3d..397d6a1f 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/nested_if.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/nested_if.rs @@ -21,6 +21,7 @@ use super::BTreeMap; use super::{AstToJoinIrLowerer, ExtractCtx, JoinFunction, JoinInst, JoinModule}; +use crate::mir::join_ir::JoinIrPhase; impl AstToJoinIrLowerer { /// Phase 41-4.2: ネスト if パターンの lowering @@ -193,6 +194,7 @@ impl AstToJoinIrLowerer { JoinModule { functions, entry: Some(func_id), + phase: JoinIrPhase::Structured, } } diff --git a/src/mir/join_ir/frontend/ast_lowerer/read_quoted.rs b/src/mir/join_ir/frontend/ast_lowerer/read_quoted.rs index e1eef027..d41121d6 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/read_quoted.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/read_quoted.rs @@ -29,6 +29,7 @@ use super::BTreeMap; use super::{ AstToJoinIrLowerer, ConstValue, ExtractCtx, JoinFunction, JoinInst, JoinModule, MergePair, }; +use crate::mir::join_ir::JoinIrPhase; impl AstToJoinIrLowerer { /// Phase 45: read_quoted_from パターンの lowering @@ -494,6 +495,7 @@ impl AstToJoinIrLowerer { JoinModule { functions, entry: Some(entry_id), + phase: JoinIrPhase::Structured, } } } diff --git a/src/mir/join_ir/lowering/bool_expr_lowerer.rs b/src/mir/join_ir/lowering/bool_expr_lowerer.rs index 38e8e6fc..8e839b61 100644 --- a/src/mir/join_ir/lowering/bool_expr_lowerer.rs +++ b/src/mir/join_ir/lowering/bool_expr_lowerer.rs @@ -110,13 +110,12 @@ impl<'a> BoolExprLowerer<'a> { }; // Emit Compare instruction - self.builder - .emit_instruction(MirInstruction::Compare { - dst, - op: cmp_op, - lhs, - rhs, - })?; + self.builder.emit_instruction(MirInstruction::Compare { + dst, + op: cmp_op, + lhs, + rhs, + })?; // Mark result type as Bool self.builder.value_types.insert(dst, MirType::Bool); @@ -130,13 +129,12 @@ impl<'a> BoolExprLowerer<'a> { let dst = self.builder.next_value_id(); // Emit BinOp And instruction - self.builder - .emit_instruction(MirInstruction::BinOp { - dst, - op: BinaryOp::BitAnd, // Use BitAnd for logical AND - lhs, - rhs, - })?; + self.builder.emit_instruction(MirInstruction::BinOp { + dst, + op: BinaryOp::BitAnd, // Use BitAnd for logical AND + lhs, + rhs, + })?; // Mark result type as Bool self.builder.value_types.insert(dst, MirType::Bool); @@ -150,13 +148,12 @@ impl<'a> BoolExprLowerer<'a> { let dst = self.builder.next_value_id(); // Emit BinOp Or instruction - self.builder - .emit_instruction(MirInstruction::BinOp { - dst, - op: BinaryOp::BitOr, // Use BitOr for logical OR - lhs, - rhs, - })?; + self.builder.emit_instruction(MirInstruction::BinOp { + dst, + op: BinaryOp::BitOr, // Use BitOr for logical OR + lhs, + rhs, + })?; // Mark result type as Bool self.builder.value_types.insert(dst, MirType::Bool); @@ -179,12 +176,11 @@ impl<'a> BoolExprLowerer<'a> { let dst = self.builder.next_value_id(); // Emit UnaryOp Not instruction - self.builder - .emit_instruction(MirInstruction::UnaryOp { - dst, - op: crate::mir::UnaryOp::Not, - operand: operand_val, - })?; + self.builder.emit_instruction(MirInstruction::UnaryOp { + dst, + op: crate::mir::UnaryOp::Not, + operand: operand_val, + })?; // Mark result type as Bool self.builder.value_types.insert(dst, MirType::Bool); @@ -217,7 +213,7 @@ impl<'a> BoolExprLowerer<'a> { // use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span}; // use crate::mir::builder::MirBuilder; // use crate::mir::FunctionSignature; -// +// // /// Helper to create a test MirBuilder // fn create_test_builder() -> MirBuilder { // let mut builder = MirBuilder::new(); @@ -232,13 +228,13 @@ impl<'a> BoolExprLowerer<'a> { // builder.start_new_block(); // builder // } -// +// // /// Test: Simple comparison (i < 10) // #[test] // fn test_simple_comparison() { // let mut builder = create_test_builder(); // let mut lowerer = BoolExprLowerer::new(&mut builder); -// +// // // AST: i < 10 // let ast = ASTNode::BinaryOp { // operator: BinaryOperator::Less, @@ -252,17 +248,17 @@ impl<'a> BoolExprLowerer<'a> { // }), // span: Span::unknown(), // }; -// +// // let result = lowerer.lower_condition(&ast); // assert!(result.is_ok(), "Simple comparison should succeed"); // } -// +// // /// Test: OR chain (ch == " " || ch == "\t") // #[test] // fn test_or_chain() { // let mut builder = create_test_builder(); // let mut lowerer = BoolExprLowerer::new(&mut builder); -// +// // // AST: ch == " " || ch == "\t" // let ast = ASTNode::BinaryOp { // operator: BinaryOperator::Or, @@ -292,17 +288,17 @@ impl<'a> BoolExprLowerer<'a> { // }), // span: Span::unknown(), // }; -// +// // let result = lowerer.lower_condition(&ast); // assert!(result.is_ok(), "OR chain should succeed"); // } -// +// // /// Test: Complex mixed condition (i < len && (c == " " || c == "\t")) // #[test] // fn test_complex_mixed_condition() { // let mut builder = create_test_builder(); // let mut lowerer = BoolExprLowerer::new(&mut builder); -// +// // // AST: i < len && (c == " " || c == "\t") // let ast = ASTNode::BinaryOp { // operator: BinaryOperator::And, @@ -348,17 +344,17 @@ impl<'a> BoolExprLowerer<'a> { // }), // span: Span::unknown(), // }; -// +// // let result = lowerer.lower_condition(&ast); // assert!(result.is_ok(), "Complex mixed condition should succeed"); // } -// +// // /// Test: NOT operator (!condition) // #[test] // fn test_not_operator() { // let mut builder = create_test_builder(); // let mut lowerer = BoolExprLowerer::new(&mut builder); -// +// // // AST: !(i < 10) // let ast = ASTNode::UnaryOp { // operator: crate::ast::UnaryOperator::Not, diff --git a/src/mir/join_ir/lowering/carrier_info.rs b/src/mir/join_ir/lowering/carrier_info.rs index ee728c9d..fa2af155 100644 --- a/src/mir/join_ir/lowering/carrier_info.rs +++ b/src/mir/join_ir/lowering/carrier_info.rs @@ -210,15 +210,12 @@ impl CarrierInfo { variable_map: &BTreeMap, // Phase 222.5-D: HashMap → BTreeMap for determinism ) -> Result { // Find loop variable - let loop_var_id = variable_map - .get(&loop_var_name) - .copied() - .ok_or_else(|| { - format!( - "Loop variable '{}' not found in variable_map", - loop_var_name - ) - })?; + let loop_var_id = variable_map.get(&loop_var_name).copied().ok_or_else(|| { + format!( + "Loop variable '{}' not found in variable_map", + loop_var_name + ) + })?; // Collect all non-loop-var variables as carriers let mut carriers: Vec = variable_map @@ -280,9 +277,10 @@ impl CarrierInfo { let mut carriers = Vec::new(); for name in carrier_names { - let host_id = variable_map.get(&name).copied().ok_or_else(|| { - format!("Carrier variable '{}' not found in variable_map", name) - })?; + let host_id = variable_map + .get(&name) + .copied() + .ok_or_else(|| format!("Carrier variable '{}' not found in variable_map", name))?; carriers.push(CarrierVar { name, @@ -408,7 +406,9 @@ impl CarrierInfo { /// eprintln!("Whitespace chars: {:?}", helper.whitespace_chars); /// } /// ``` - pub fn trim_helper(&self) -> Option<&crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper> { + pub fn trim_helper( + &self, + ) -> Option<&crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper> { self.trim_helper.as_ref() } @@ -430,7 +430,10 @@ impl CarrierInfo { /// * `Some(ValueId)` - 対応する carrier の join_id が見つかった場合 /// * `None` - promoted_loopbodylocals に含まれない、または join_id 未設定の場合 pub fn resolve_promoted_join_id(&self, original_name: &str) -> Option { - if !self.promoted_loopbodylocals.contains(&original_name.to_string()) { + if !self + .promoted_loopbodylocals + .contains(&original_name.to_string()) + { return None; } @@ -646,29 +649,21 @@ mod tests { CarrierVar { name: name.to_string(), host_id: ValueId(id), - join_id: None, // Phase 177-STRUCT-1 + join_id: None, // Phase 177-STRUCT-1 role: CarrierRole::LoopState, // Phase 227: Default to LoopState - init: CarrierInit::FromHost, // Phase 228: Default to FromHost + init: CarrierInit::FromHost, // Phase 228: Default to FromHost } } // Helper: Create a CarrierInfo for testing fn test_carrier_info(loop_var: &str, loop_id: u32, carriers: Vec) -> CarrierInfo { - CarrierInfo::with_carriers( - loop_var.to_string(), - ValueId(loop_id), - carriers, - ) + CarrierInfo::with_carriers(loop_var.to_string(), ValueId(loop_id), carriers) } #[test] fn test_merge_from_empty() { // Merge empty CarrierInfo should not change anything - let mut carrier_info = test_carrier_info( - "i", - 5, - vec![test_carrier("sum", 10)], - ); + let mut carrier_info = test_carrier_info("i", 5, vec![test_carrier("sum", 10)]); let other = test_carrier_info("j", 20, vec![]); @@ -681,17 +676,9 @@ mod tests { #[test] fn test_merge_from_new_carrier() { // Merge a new carrier that doesn't exist yet - let mut carrier_info = test_carrier_info( - "i", - 5, - vec![test_carrier("sum", 10)], - ); + let mut carrier_info = test_carrier_info("i", 5, vec![test_carrier("sum", 10)]); - let other = test_carrier_info( - "j", - 20, - vec![test_carrier("count", 15)], - ); + let other = test_carrier_info("j", 20, vec![test_carrier("count", 15)]); carrier_info.merge_from(&other); @@ -704,11 +691,7 @@ mod tests { #[test] fn test_merge_from_duplicate_carrier() { // Merge a carrier with the same name should NOT duplicate - let mut carrier_info = test_carrier_info( - "i", - 5, - vec![test_carrier("sum", 10)], - ); + let mut carrier_info = test_carrier_info("i", 5, vec![test_carrier("sum", 10)]); let other = test_carrier_info( "j", @@ -728,19 +711,12 @@ mod tests { #[test] fn test_merge_from_multiple_carriers() { // Merge multiple carriers - let mut carrier_info = test_carrier_info( - "i", - 5, - vec![test_carrier("sum", 10)], - ); + let mut carrier_info = test_carrier_info("i", 5, vec![test_carrier("sum", 10)]); let other = test_carrier_info( "j", 20, - vec![ - test_carrier("count", 15), - test_carrier("product", 18), - ], + vec![test_carrier("count", 15), test_carrier("product", 18)], ); carrier_info.merge_from(&other); @@ -758,19 +734,13 @@ mod tests { let mut carrier_info = test_carrier_info( "i", 5, - vec![ - test_carrier("zebra", 30), - test_carrier("alpha", 10), - ], + vec![test_carrier("zebra", 30), test_carrier("alpha", 10)], ); let other = test_carrier_info( "j", 20, - vec![ - test_carrier("beta", 15), - test_carrier("gamma", 18), - ], + vec![test_carrier("beta", 15), test_carrier("gamma", 18)], ); carrier_info.merge_from(&other); diff --git a/src/mir/join_ir/lowering/carrier_update_emitter.rs b/src/mir/join_ir/lowering/carrier_update_emitter.rs index 3782f9c5..dc713804 100644 --- a/src/mir/join_ir/lowering/carrier_update_emitter.rs +++ b/src/mir/join_ir/lowering/carrier_update_emitter.rs @@ -67,12 +67,7 @@ pub fn emit_carrier_update_with_env( // Get carrier parameter ValueId from env let carrier_param = env .resolve(&carrier.name) - .ok_or_else(|| { - format!( - "Carrier '{}' not found in UpdateEnv", - carrier.name - ) - })?; + .ok_or_else(|| format!("Carrier '{}' not found in UpdateEnv", carrier.name))?; // Allocate result ValueId let result = alloc_value(); @@ -99,12 +94,7 @@ pub fn emit_carrier_update_with_env( // Get carrier parameter ValueId from env let carrier_param = env .resolve(&carrier.name) - .ok_or_else(|| { - format!( - "Carrier '{}' not found in UpdateEnv", - carrier.name - ) - })?; + .ok_or_else(|| format!("Carrier '{}' not found in UpdateEnv", carrier.name))?; // Resolve RHS (Phase 184: Now supports body-local variables!) let rhs_id = match rhs { @@ -255,12 +245,7 @@ pub fn emit_carrier_update( // Get carrier parameter ValueId from env let carrier_param = env .get(&carrier.name) - .ok_or_else(|| { - format!( - "Carrier '{}' not found in ConditionEnv", - carrier.name - ) - })?; + .ok_or_else(|| format!("Carrier '{}' not found in ConditionEnv", carrier.name))?; // Allocate result ValueId let result = alloc_value(); @@ -287,12 +272,7 @@ pub fn emit_carrier_update( // Get carrier parameter ValueId from env let carrier_param = env .get(&carrier.name) - .ok_or_else(|| { - format!( - "Carrier '{}' not found in ConditionEnv", - carrier.name - ) - })?; + .ok_or_else(|| format!("Carrier '{}' not found in ConditionEnv", carrier.name))?; // Resolve RHS let rhs_id = match rhs { @@ -304,14 +284,12 @@ pub fn emit_carrier_update( })); const_id } - UpdateRhs::Variable(var_name) => { - env.get(var_name).ok_or_else(|| { - format!( - "Update RHS variable '{}' not found in ConditionEnv", - var_name - ) - })? - } + UpdateRhs::Variable(var_name) => env.get(var_name).ok_or_else(|| { + format!( + "Update RHS variable '{}' not found in ConditionEnv", + var_name + ) + })?, // Phase 188: String updates now emit JoinIR BinOp // StringAppendLiteral: s = s + "literal" UpdateRhs::StringLiteral(s) => { @@ -441,13 +419,8 @@ mod tests { }; let mut instructions = Vec::new(); - let result = emit_carrier_update( - &carrier, - &update, - &mut alloc_value, - &env, - &mut instructions, - ); + let result = + emit_carrier_update(&carrier, &update, &mut alloc_value, &env, &mut instructions); assert!(result.is_ok()); let result_id = result.unwrap(); @@ -497,13 +470,8 @@ mod tests { }; let mut instructions = Vec::new(); - let result = emit_carrier_update( - &carrier, - &update, - &mut alloc_value, - &env, - &mut instructions, - ); + let result = + emit_carrier_update(&carrier, &update, &mut alloc_value, &env, &mut instructions); assert!(result.is_ok()); let result_id = result.unwrap(); @@ -553,13 +521,8 @@ mod tests { }; let mut instructions = Vec::new(); - let result = emit_carrier_update( - &carrier, - &update, - &mut alloc_value, - &env, - &mut instructions, - ); + let result = + emit_carrier_update(&carrier, &update, &mut alloc_value, &env, &mut instructions); assert!(result.is_ok()); let result_id = result.unwrap(); @@ -596,13 +559,8 @@ mod tests { }; let mut instructions = Vec::new(); - let result = emit_carrier_update( - &carrier, - &update, - &mut alloc_value, - &env, - &mut instructions, - ); + let result = + emit_carrier_update(&carrier, &update, &mut alloc_value, &env, &mut instructions); assert!(result.is_err()); assert!(result.unwrap_err().contains("Carrier 'unknown' not found")); @@ -627,13 +585,8 @@ mod tests { }; let mut instructions = Vec::new(); - let result = emit_carrier_update( - &carrier, - &update, - &mut alloc_value, - &env, - &mut instructions, - ); + let result = + emit_carrier_update(&carrier, &update, &mut alloc_value, &env, &mut instructions); assert!(result.is_err()); assert!(result.unwrap_err().contains("doesn't match carrier")); @@ -658,16 +611,13 @@ mod tests { }; let mut instructions = Vec::new(); - let result = emit_carrier_update( - &carrier, - &update, - &mut alloc_value, - &env, - &mut instructions, - ); + let result = + emit_carrier_update(&carrier, &update, &mut alloc_value, &env, &mut instructions); assert!(result.is_err()); - assert!(result.unwrap_err().contains("Update RHS variable 'unknown_var' not found")); + assert!(result + .unwrap_err() + .contains("Update RHS variable 'unknown_var' not found")); } // ============================================================================ @@ -765,7 +715,12 @@ mod tests { // Should use x=100 (condition env), not x=200 (body-local env) match &instructions[0] { - JoinInst::Compute(MirLikeInst::BinOp { dst: _, op: _, lhs: _, rhs }) => { + JoinInst::Compute(MirLikeInst::BinOp { + dst: _, + op: _, + lhs: _, + rhs, + }) => { assert_eq!(*rhs, ValueId(100)); // Condition env wins } _ => panic!("Expected BinOp instruction"), diff --git a/src/mir/join_ir/lowering/complex_addend_normalizer.rs b/src/mir/join_ir/lowering/complex_addend_normalizer.rs index a8ca5b63..f36522b0 100644 --- a/src/mir/join_ir/lowering/complex_addend_normalizer.rs +++ b/src/mir/join_ir/lowering/complex_addend_normalizer.rs @@ -107,7 +107,8 @@ impl ComplexAddendNormalizer { left, right, .. - } | ASTNode::BinaryOp { + } + | ASTNode::BinaryOp { operator: BinaryOperator::Subtract, left, right, diff --git a/src/mir/join_ir/lowering/condition_env.rs b/src/mir/join_ir/lowering/condition_env.rs index 362c970b..ce391d6a 100644 --- a/src/mir/join_ir/lowering/condition_env.rs +++ b/src/mir/join_ir/lowering/condition_env.rs @@ -61,7 +61,7 @@ impl ConditionEnv { pub fn new() -> Self { Self { name_to_join: BTreeMap::new(), // Phase 222.5-D: HashMap → BTreeMap for determinism - captured: BTreeMap::new(), // Phase 222.5-D: HashMap → BTreeMap for determinism + captured: BTreeMap::new(), // Phase 222.5-D: HashMap → BTreeMap for determinism } } @@ -82,7 +82,9 @@ impl ConditionEnv { /// Returns `Some(ValueId)` if the variable exists in the environment, /// `None` otherwise. pub fn get(&self, name: &str) -> Option { - self.name_to_join.get(name).copied() + self.name_to_join + .get(name) + .copied() .or_else(|| self.captured.get(name).copied()) } @@ -126,7 +128,9 @@ impl ConditionEnv { /// /// Phase 200-B: Includes both name_to_join and captured variables. pub fn names(&self) -> Vec { - let mut names: Vec<_> = self.name_to_join.keys() + let mut names: Vec<_> = self + .name_to_join + .keys() .chain(self.captured.keys()) .cloned() .collect(); @@ -235,8 +239,8 @@ mod tests { fn test_condition_binding() { let binding = ConditionBinding::new( "start".to_string(), - ValueId(33), // HOST - ValueId(1), // JoinIR + ValueId(33), // HOST + ValueId(1), // JoinIR ); assert_eq!(binding.name, "start"); diff --git a/src/mir/join_ir/lowering/condition_lowerer.rs b/src/mir/join_ir/lowering/condition_lowerer.rs index b8d7840f..fb4e81c3 100644 --- a/src/mir/join_ir/lowering/condition_lowerer.rs +++ b/src/mir/join_ir/lowering/condition_lowerer.rs @@ -100,12 +100,8 @@ where | BinaryOperator::Greater => { lower_comparison(operator, left, right, alloc_value, env, instructions) } - BinaryOperator::And => { - lower_logical_and(left, right, alloc_value, env, instructions) - } - BinaryOperator::Or => { - lower_logical_or(left, right, alloc_value, env, instructions) - } + BinaryOperator::And => lower_logical_and(left, right, alloc_value, env, instructions), + BinaryOperator::Or => lower_logical_or(left, right, alloc_value, env, instructions), _ => Err(format!( "Unsupported binary operator in condition: {:?}", operator @@ -264,7 +260,10 @@ where return Err("Float literals not supported in JoinIR conditions yet".to_string()); } _ => { - return Err(format!("Unsupported literal type in condition: {:?}", value)); + return Err(format!( + "Unsupported literal type in condition: {:?}", + value + )); } }; @@ -317,7 +316,14 @@ where let recv_val = lower_value_expression(object, alloc_value, env, instructions)?; // 2. Lower method call using MethodCallLowerer (will lower arguments internally) - MethodCallLowerer::lower_for_condition(recv_val, method, arguments, alloc_value, env, instructions) + MethodCallLowerer::lower_for_condition( + recv_val, + method, + arguments, + alloc_value, + env, + instructions, + ) } _ => Err(format!( diff --git a/src/mir/join_ir/lowering/condition_lowering_box.rs b/src/mir/join_ir/lowering/condition_lowering_box.rs index df9bc890..058995a7 100644 --- a/src/mir/join_ir/lowering/condition_lowering_box.rs +++ b/src/mir/join_ir/lowering/condition_lowering_box.rs @@ -15,9 +15,9 @@ //! **Fail-Safe**: Implementations return explicit errors for unsupported patterns, //! allowing callers to fall back to alternative paths. +use super::scope_manager::ScopeManager; use crate::ast::ASTNode; use crate::mir::ValueId; -use super::scope_manager::ScopeManager; /// Phase 244: Context for condition lowering /// @@ -158,12 +158,12 @@ pub trait ConditionLoweringBox { #[cfg(test)] mod tests { use super::*; - use crate::ast::{Span, LiteralValue, BinaryOperator}; + use crate::ast::{BinaryOperator, LiteralValue, Span}; use crate::mir::builder::MirBuilder; - use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager; - use crate::mir::join_ir::lowering::condition_env::ConditionEnv; use crate::mir::join_ir::lowering::carrier_info::CarrierInfo; - use crate::mir::join_ir::lowering::expr_lowerer::{ExprLowerer, ExprContext}; + use crate::mir::join_ir::lowering::condition_env::ConditionEnv; + use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer}; + use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager; fn span() -> Span { Span::unknown() diff --git a/src/mir/join_ir/lowering/condition_pattern.rs b/src/mir/join_ir/lowering/condition_pattern.rs index b590c30c..c19f896b 100644 --- a/src/mir/join_ir/lowering/condition_pattern.rs +++ b/src/mir/join_ir/lowering/condition_pattern.rs @@ -79,7 +79,12 @@ pub enum ConditionPattern { pub fn analyze_condition_pattern(cond: &ASTNode) -> ConditionPattern { match cond { // Comparison operators: ==, !=, <, >, <=, >= - ASTNode::BinaryOp { operator, left, right, .. } => { + ASTNode::BinaryOp { + operator, + left, + right, + .. + } => { // Check if operator is a comparison let is_comparison = matches!( operator, @@ -225,12 +230,12 @@ pub struct NormalizedCondition { /// ``` fn flip_compare_op(op: CompareOp) -> CompareOp { match op { - CompareOp::Lt => CompareOp::Gt, // < → > - CompareOp::Gt => CompareOp::Lt, // > → < - CompareOp::Le => CompareOp::Ge, // <= → >= - CompareOp::Ge => CompareOp::Le, // >= → <= - CompareOp::Eq => CompareOp::Eq, // == → == (不変) - CompareOp::Ne => CompareOp::Ne, // != → != (不変) + CompareOp::Lt => CompareOp::Gt, // < → > + CompareOp::Gt => CompareOp::Lt, // > → < + CompareOp::Le => CompareOp::Ge, // <= → >= + CompareOp::Ge => CompareOp::Le, // >= → <= + CompareOp::Eq => CompareOp::Eq, // == → == (不変) + CompareOp::Ne => CompareOp::Ne, // != → != (不変) } } @@ -278,12 +283,24 @@ fn binary_op_to_compare_op(op: &BinaryOperator) -> Option { /// ``` pub fn normalize_comparison(cond: &ASTNode) -> Option { match cond { - ASTNode::BinaryOp { operator, left, right, .. } => { + ASTNode::BinaryOp { + operator, + left, + right, + .. + } => { // Comparison operator のみ受理 let compare_op = binary_op_to_compare_op(operator)?; // Case 1: var CmpOp literal (e.g., i > 0) - if let (ASTNode::Variable { name: left_var, .. }, ASTNode::Literal { value: LiteralValue::Integer(right_val), .. }) = (left.as_ref(), right.as_ref()) { + if let ( + ASTNode::Variable { name: left_var, .. }, + ASTNode::Literal { + value: LiteralValue::Integer(right_val), + .. + }, + ) = (left.as_ref(), right.as_ref()) + { return Some(NormalizedCondition { left_var: left_var.clone(), op: compare_op, @@ -292,7 +309,16 @@ pub fn normalize_comparison(cond: &ASTNode) -> Option { } // Case 2: literal CmpOp var (e.g., 0 < i) → 左右反転 - if let (ASTNode::Literal { value: LiteralValue::Integer(left_val), .. }, ASTNode::Variable { name: right_var, .. }) = (left.as_ref(), right.as_ref()) { + if let ( + ASTNode::Literal { + value: LiteralValue::Integer(left_val), + .. + }, + ASTNode::Variable { + name: right_var, .. + }, + ) = (left.as_ref(), right.as_ref()) + { return Some(NormalizedCondition { left_var: right_var.clone(), op: flip_compare_op(compare_op), // 演算子を反転 @@ -301,7 +327,13 @@ pub fn normalize_comparison(cond: &ASTNode) -> Option { } // Case 3: var CmpOp var (e.g., i > j) - if let (ASTNode::Variable { name: left_var, .. }, ASTNode::Variable { name: right_var, .. }) = (left.as_ref(), right.as_ref()) { + if let ( + ASTNode::Variable { name: left_var, .. }, + ASTNode::Variable { + name: right_var, .. + }, + ) = (left.as_ref(), right.as_ref()) + { return Some(NormalizedCondition { left_var: left_var.clone(), op: compare_op, @@ -351,7 +383,10 @@ mod tests { fn test_simple_comparison_greater() { // i > 0 let cond = binop(BinaryOperator::Greater, var("i"), int_lit(0)); - assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison); + assert_eq!( + analyze_condition_pattern(&cond), + ConditionPattern::SimpleComparison + ); assert!(is_simple_comparison(&cond)); } @@ -359,7 +394,10 @@ mod tests { fn test_simple_comparison_less() { // i < 10 let cond = binop(BinaryOperator::Less, var("i"), int_lit(10)); - assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison); + assert_eq!( + analyze_condition_pattern(&cond), + ConditionPattern::SimpleComparison + ); assert!(is_simple_comparison(&cond)); } @@ -367,7 +405,10 @@ mod tests { fn test_simple_comparison_equal() { // i == 5 let cond = binop(BinaryOperator::Equal, var("i"), int_lit(5)); - assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison); + assert_eq!( + analyze_condition_pattern(&cond), + ConditionPattern::SimpleComparison + ); assert!(is_simple_comparison(&cond)); } @@ -375,7 +416,10 @@ mod tests { fn test_simple_comparison_not_equal() { // i != 0 let cond = binop(BinaryOperator::NotEqual, var("i"), int_lit(0)); - assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison); + assert_eq!( + analyze_condition_pattern(&cond), + ConditionPattern::SimpleComparison + ); assert!(is_simple_comparison(&cond)); } @@ -384,7 +428,10 @@ mod tests { // Phase 242-EX-A: i % 2 == 1 (BinaryOp in LHS) is now SimpleComparison let lhs = binop(BinaryOperator::Modulo, var("i"), int_lit(2)); let cond = binop(BinaryOperator::Equal, lhs, int_lit(1)); - assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison); + assert_eq!( + analyze_condition_pattern(&cond), + ConditionPattern::SimpleComparison + ); assert!(is_simple_comparison(&cond)); } @@ -393,7 +440,10 @@ mod tests { // Phase 242-EX-A: i == a + b (BinaryOp in RHS) is now SimpleComparison let rhs = binop(BinaryOperator::Add, var("a"), var("b")); let cond = binop(BinaryOperator::Equal, var("i"), rhs); - assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison); + assert_eq!( + analyze_condition_pattern(&cond), + ConditionPattern::SimpleComparison + ); assert!(is_simple_comparison(&cond)); } @@ -402,7 +452,10 @@ mod tests { // Pattern3/4 で使う典型的なフィルタ条件: i % 2 == 1 let lhs = binop(BinaryOperator::Modulo, var("i"), int_lit(2)); let cond = binop(BinaryOperator::Equal, lhs, int_lit(1)); - assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison); + assert_eq!( + analyze_condition_pattern(&cond), + ConditionPattern::SimpleComparison + ); assert!(is_simple_comparison(&cond)); } @@ -535,7 +588,10 @@ mod tests { fn test_analyze_pattern_literal_cmp_var() { // Phase 222: 0 < i → SimpleComparison let cond = binop(BinaryOperator::Less, int_lit(0), var("i")); - assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison); + assert_eq!( + analyze_condition_pattern(&cond), + ConditionPattern::SimpleComparison + ); assert!(is_simple_comparison(&cond)); } @@ -543,7 +599,10 @@ mod tests { fn test_analyze_pattern_var_cmp_var() { // Phase 222: i > j → SimpleComparison let cond = binop(BinaryOperator::Greater, var("i"), var("j")); - assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison); + assert_eq!( + analyze_condition_pattern(&cond), + ConditionPattern::SimpleComparison + ); assert!(is_simple_comparison(&cond)); } } diff --git a/src/mir/join_ir/lowering/condition_var_extractor.rs b/src/mir/join_ir/lowering/condition_var_extractor.rs index 0fd970fd..1dd8c4d1 100644 --- a/src/mir/join_ir/lowering/condition_var_extractor.rs +++ b/src/mir/join_ir/lowering/condition_var_extractor.rs @@ -40,10 +40,7 @@ use std::collections::BTreeSet; /// ); /// // Result: ["end", "len", "start"] (sorted, 'i' excluded) /// ``` -pub fn extract_condition_variables( - cond_ast: &ASTNode, - exclude_vars: &[String], -) -> Vec { +pub fn extract_condition_variables(cond_ast: &ASTNode, exclude_vars: &[String]) -> Vec { let mut all_vars = BTreeSet::new(); collect_variables_recursive(cond_ast, &mut all_vars); diff --git a/src/mir/join_ir/lowering/digitpos_condition_normalizer.rs b/src/mir/join_ir/lowering/digitpos_condition_normalizer.rs index 0ac6e10a..9a9fb3cd 100644 --- a/src/mir/join_ir/lowering/digitpos_condition_normalizer.rs +++ b/src/mir/join_ir/lowering/digitpos_condition_normalizer.rs @@ -152,11 +152,7 @@ mod tests { #[test] fn test_normalize_digit_pos_lt_zero() { // Pattern: digit_pos < 0 - let cond = binary_op( - var_node("digit_pos"), - BinaryOperator::Less, - int_literal(0), - ); + let cond = binary_op(var_node("digit_pos"), BinaryOperator::Less, int_literal(0)); let normalized = DigitPosConditionNormalizer::normalize(&cond, "digit_pos", "is_digit_pos"); @@ -166,14 +162,12 @@ mod tests { operator: UnaryOperator::Not, operand, .. - } => { - match operand.as_ref() { - ASTNode::Variable { name, .. } => { - assert_eq!(name, "is_digit_pos"); - } - _ => panic!("Expected Variable node"), + } => match operand.as_ref() { + ASTNode::Variable { name, .. } => { + assert_eq!(name, "is_digit_pos"); } - } + _ => panic!("Expected Variable node"), + }, _ => panic!("Expected UnaryOp Not node"), } } @@ -201,21 +195,13 @@ mod tests { #[test] fn test_no_normalize_wrong_variable() { // Pattern: other_var < 0 (different variable name) - let cond = binary_op( - var_node("other_var"), - BinaryOperator::Less, - int_literal(0), - ); + let cond = binary_op(var_node("other_var"), BinaryOperator::Less, int_literal(0)); let normalized = DigitPosConditionNormalizer::normalize(&cond, "digit_pos", "is_digit_pos"); // Should NOT transform (return original) match normalized { - ASTNode::BinaryOp { - operator, - left, - .. - } => { + ASTNode::BinaryOp { operator, left, .. } => { assert_eq!(operator, BinaryOperator::Less); match left.as_ref() { ASTNode::Variable { name, .. } => { @@ -231,20 +217,14 @@ mod tests { #[test] fn test_no_normalize_wrong_constant() { // Pattern: digit_pos < 10 (different constant, not 0) - let cond = binary_op( - var_node("digit_pos"), - BinaryOperator::Less, - int_literal(10), - ); + let cond = binary_op(var_node("digit_pos"), BinaryOperator::Less, int_literal(10)); let normalized = DigitPosConditionNormalizer::normalize(&cond, "digit_pos", "is_digit_pos"); // Should NOT transform (return original) match normalized { ASTNode::BinaryOp { - operator, - right, - .. + operator, right, .. } => { assert_eq!(operator, BinaryOperator::Less); match right.as_ref() { diff --git a/src/mir/join_ir/lowering/expr_lowerer.rs b/src/mir/join_ir/lowering/expr_lowerer.rs index 19d64eb2..7fbde599 100644 --- a/src/mir/join_ir/lowering/expr_lowerer.rs +++ b/src/mir/join_ir/lowering/expr_lowerer.rs @@ -15,12 +15,12 @@ //! **Fail-Safe**: Unsupported AST nodes return explicit errors, allowing callers //! to fall back to legacy paths. -use crate::ast::ASTNode; -use crate::mir::ValueId; -use crate::mir::join_ir::JoinInst; -use crate::mir::builder::MirBuilder; -use super::scope_manager::ScopeManager; use super::condition_lowerer::lower_condition_to_joinir; +use super::scope_manager::ScopeManager; +use crate::ast::ASTNode; +use crate::mir::builder::MirBuilder; +use crate::mir::join_ir::JoinInst; +use crate::mir::ValueId; mod ast_support; mod scope_resolution; @@ -165,11 +165,9 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> { pub fn lower(&mut self, ast: &ASTNode) -> Result { match self.context { ExprContext::Condition => self.lower_condition(ast), - ExprContext::General => { - Err(ExprLoweringError::UnsupportedNode( - "General expression context not yet implemented (Phase 231)".to_string() - )) - } + ExprContext::General => Err(ExprLoweringError::UnsupportedNode( + "General expression context not yet implemented (Phase 231)".to_string(), + )), } } @@ -185,9 +183,10 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> { fn lower_condition(&mut self, ast: &ASTNode) -> Result { // 1. Check if AST is supported in condition context if !ast_support::is_supported_condition(ast) { - return Err(ExprLoweringError::UnsupportedNode( - format!("Unsupported condition node: {:?}", ast) - )); + return Err(ExprLoweringError::UnsupportedNode(format!( + "Unsupported condition node: {:?}", + ast + ))); } // 2. Build ConditionEnv from ScopeManager @@ -204,17 +203,18 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> { id }; - let (result_value, instructions) = lower_condition_to_joinir( - ast, - &mut alloc_value, - &condition_env, - ).map_err(|e| ExprLoweringError::LoweringError(e))?; + let (result_value, instructions) = + lower_condition_to_joinir(ast, &mut alloc_value, &condition_env) + .map_err(|e| ExprLoweringError::LoweringError(e))?; // Phase 235: 保存しておき、テストから観察できるようにする self.last_instructions = instructions; if self.debug { - eprintln!("[expr_lowerer/phase231] Lowered condition → ValueId({:?})", result_value); + eprintln!( + "[expr_lowerer/phase231] Lowered condition → ValueId({:?})", + result_value + ); } Ok(result_value) @@ -230,7 +230,7 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> { // Phase 244: ConditionLoweringBox trait implementation // ============================================================================ -use super::condition_lowering_box::{ConditionLoweringBox, ConditionContext}; +use super::condition_lowering_box::{ConditionContext, ConditionLoweringBox}; impl<'env, 'builder, S: ScopeManager> ConditionLoweringBox for ExprLowerer<'env, 'builder, S> { /// Phase 244: Implement ConditionLoweringBox trait for ExprLowerer @@ -280,13 +280,13 @@ impl<'env, 'builder, S: ScopeManager> ConditionLoweringBox for ExprLowerer<'e #[cfg(test)] mod tests { + use super::test_helpers::{bin, lit_i, span, var}; use super::*; use crate::ast::{BinaryOperator, LiteralValue, Span, UnaryOperator}; use crate::mir::join_ir::lowering::carrier_info::CarrierInfo; use crate::mir::join_ir::lowering::condition_env::ConditionEnv; use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager; use crate::mir::join_ir::{BinOpKind, MirLikeInst, UnaryOp as JoinUnaryOp}; - use super::test_helpers::{bin, lit_i, span, var}; // Helper to create a test MirBuilder (Phase 231: minimal stub) fn create_test_builder() -> MirBuilder { @@ -340,7 +340,10 @@ mod tests { let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); let result = expr_lowerer.lower(&ast); - assert!(result.is_ok(), "Should lower simple comparison successfully"); + assert!( + result.is_ok(), + "Should lower simple comparison successfully" + ); } #[test] @@ -381,7 +384,10 @@ mod tests { let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); let result = expr_lowerer.lower(&ast); - assert!(matches!(result, Err(ExprLoweringError::VariableNotFound(_)))); + assert!(matches!( + result, + Err(ExprLoweringError::VariableNotFound(_)) + )); } #[test] @@ -406,7 +412,9 @@ mod tests { let mut builder = create_test_builder(); // AST: Break (unsupported in condition context) - let ast = ASTNode::Break { span: Span::unknown() }; + let ast = ASTNode::Break { + span: Span::unknown(), + }; let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); let result = expr_lowerer.lower(&ast); @@ -429,7 +437,9 @@ mod tests { }), span: Span::unknown(), }; - assert!(ExprLowerer::::is_supported_condition(&ast)); + assert!(ExprLowerer::::is_supported_condition( + &ast + )); // Supported: MethodCall let ast = ASTNode::MethodCall { @@ -441,10 +451,14 @@ mod tests { arguments: vec![], span: Span::unknown(), }; - assert!(ExprLowerer::::is_supported_condition(&ast)); + assert!(ExprLowerer::::is_supported_condition( + &ast + )); // Unsupported: Break node - let ast = ASTNode::Break { span: Span::unknown() }; + let ast = ASTNode::Break { + span: Span::unknown(), + }; assert!(!ExprLowerer::::is_supported_condition(&ast)); } @@ -507,10 +521,9 @@ mod tests { fn assert_has_compare(instructions: &[JoinInst]) { assert!( - instructions.iter().any(|inst| matches!( - inst, - JoinInst::Compute(MirLikeInst::Compare { .. } - ))), + instructions + .iter() + .any(|inst| matches!(inst, JoinInst::Compute(MirLikeInst::Compare { .. }))), "Expected at least one Compare instruction, got {:?}", instructions ); @@ -532,8 +545,11 @@ mod tests { assert!( instructions.iter().any(|inst| matches!( inst, - JoinInst::Compute(MirLikeInst::UnaryOp { op: JoinUnaryOp::Not, .. } - ))), + JoinInst::Compute(MirLikeInst::UnaryOp { + op: JoinUnaryOp::Not, + .. + }) + )), "Expected at least one UnaryOp::Not, got {:?}", instructions ); diff --git a/src/mir/join_ir/lowering/expr_lowerer/ast_support.rs b/src/mir/join_ir/lowering/expr_lowerer/ast_support.rs index d39dfa08..b6fad095 100644 --- a/src/mir/join_ir/lowering/expr_lowerer/ast_support.rs +++ b/src/mir/join_ir/lowering/expr_lowerer/ast_support.rs @@ -5,14 +5,19 @@ pub(crate) fn is_supported_condition(ast: &ASTNode) -> bool { match ast { Literal { .. } => true, Variable { .. } => true, - BinaryOp { operator, left, right, .. } => { + BinaryOp { + operator, + left, + right, + .. + } => { is_supported_binary_op(operator) && is_supported_condition(left) && is_supported_condition(right) } - UnaryOp { operator, operand, .. } => { - matches!(operator, UnaryOperator::Not) && is_supported_condition(operand) - } + UnaryOp { + operator, operand, .. + } => matches!(operator, UnaryOperator::Not) && is_supported_condition(operand), MethodCall { .. } => is_supported_method_call(ast), _ => false, } @@ -20,7 +25,10 @@ pub(crate) fn is_supported_condition(ast: &ASTNode) -> bool { fn is_supported_binary_op(op: &BinaryOperator) -> bool { use BinaryOperator::*; - matches!(op, Less | LessEqual | Greater | GreaterEqual | Equal | NotEqual | And | Or) + matches!( + op, + Less | LessEqual | Greater | GreaterEqual | Equal | NotEqual | And | Or + ) } fn is_supported_method_call(ast: &ASTNode) -> bool { diff --git a/src/mir/join_ir/lowering/expr_lowerer/scope_resolution.rs b/src/mir/join_ir/lowering/expr_lowerer/scope_resolution.rs index 419efce9..90dc899c 100644 --- a/src/mir/join_ir/lowering/expr_lowerer/scope_resolution.rs +++ b/src/mir/join_ir/lowering/expr_lowerer/scope_resolution.rs @@ -1,6 +1,6 @@ +use super::{ExprLoweringError, ScopeManager}; use crate::ast::ASTNode; use crate::mir::join_ir::lowering::condition_env::ConditionEnv; -use super::{ExprLoweringError, ScopeManager}; pub(crate) fn build_condition_env_from_scope( scope: &S, @@ -30,7 +30,9 @@ fn collect_vars(ast: &ASTNode, vars: &mut Vec) { collect_vars(right, vars); } ASTNode::UnaryOp { operand, .. } => collect_vars(operand, vars), - ASTNode::MethodCall { object, arguments, .. } => { + ASTNode::MethodCall { + object, arguments, .. + } => { collect_vars(object, vars); for arg in arguments { collect_vars(arg, vars); diff --git a/src/mir/join_ir/lowering/generic_case_a/append_defs.rs b/src/mir/join_ir/lowering/generic_case_a/append_defs.rs index 92cb91cd..0ff349de 100644 --- a/src/mir/join_ir/lowering/generic_case_a/append_defs.rs +++ b/src/mir/join_ir/lowering/generic_case_a/append_defs.rs @@ -30,7 +30,6 @@ //! - `value_id_ranges::funcscanner_append_defs` - ValueId allocation strategy //! - `loop_scope_shape::CaseAContext` - Context extraction - use crate::mir::join_ir::lowering::loop_scope_shape::CaseAContext; use crate::mir::join_ir::lowering::value_id_ranges; use crate::mir::join_ir::{ diff --git a/src/mir/join_ir/lowering/generic_case_a/entry_builder.rs b/src/mir/join_ir/lowering/generic_case_a/entry_builder.rs index 538575c0..58d9140c 100644 --- a/src/mir/join_ir/lowering/generic_case_a/entry_builder.rs +++ b/src/mir/join_ir/lowering/generic_case_a/entry_builder.rs @@ -4,8 +4,8 @@ //! - ValueId/BTreeMap の初期化ボイラープレート統一化 //! - Pinned/Carrier 変数の一元管理 -use std::collections::BTreeMap; use crate::mir::ValueId; +use std::collections::BTreeMap; /// Entry関数構築用の統一ビルダー /// diff --git a/src/mir/join_ir/lowering/generic_case_a/mod.rs b/src/mir/join_ir/lowering/generic_case_a/mod.rs index c76e7edd..8b819a6d 100644 --- a/src/mir/join_ir/lowering/generic_case_a/mod.rs +++ b/src/mir/join_ir/lowering/generic_case_a/mod.rs @@ -84,19 +84,19 @@ //! - `loop_to_join` - Main loop lowering coordinator // Pattern-specific lowering modules -pub mod skip_ws; -pub mod trim; pub mod append_defs; +pub mod skip_ws; pub mod stage1_using_resolver; +pub mod trim; // Helper modules pub mod entry_builder; pub mod whitespace_check; // Re-export public lowering functions -pub(crate) use skip_ws::lower_case_a_skip_ws_with_scope; -pub(crate) use trim::lower_case_a_trim_with_scope; pub(crate) use append_defs::lower_case_a_append_defs_with_scope; +pub(crate) use skip_ws::lower_case_a_skip_ws_with_scope; pub(crate) use stage1_using_resolver::lower_case_a_stage1_usingresolver_with_scope; +pub(crate) use trim::lower_case_a_trim_with_scope; // Re-export helper utilities diff --git a/src/mir/join_ir/lowering/generic_case_a/skip_ws.rs b/src/mir/join_ir/lowering/generic_case_a/skip_ws.rs index 9eed347c..6d899c09 100644 --- a/src/mir/join_ir/lowering/generic_case_a/skip_ws.rs +++ b/src/mir/join_ir/lowering/generic_case_a/skip_ws.rs @@ -31,7 +31,6 @@ //! - `value_id_ranges::skip_ws` - ValueId allocation strategy //! - `loop_scope_shape::CaseAContext` - Context extraction - use crate::mir::join_ir::lowering::loop_scope_shape::CaseAContext; use crate::mir::join_ir::lowering::value_id_ranges; use crate::mir::join_ir::lowering::value_id_ranges::skip_ws as vid; diff --git a/src/mir/join_ir/lowering/generic_case_a/stage1_using_resolver.rs b/src/mir/join_ir/lowering/generic_case_a/stage1_using_resolver.rs index c2b1c7ce..36a3ef63 100644 --- a/src/mir/join_ir/lowering/generic_case_a/stage1_using_resolver.rs +++ b/src/mir/join_ir/lowering/generic_case_a/stage1_using_resolver.rs @@ -30,7 +30,6 @@ //! - `value_id_ranges::stage1_using_resolver` - ValueId allocation strategy //! - `loop_scope_shape::CaseAContext` - Context extraction - use crate::mir::join_ir::lowering::loop_scope_shape::CaseAContext; use crate::mir::join_ir::lowering::value_id_ranges; use crate::mir::join_ir::lowering::value_id_ranges::stage1_using_resolver as stage1_vid; diff --git a/src/mir/join_ir/lowering/generic_case_a/trim.rs b/src/mir/join_ir/lowering/generic_case_a/trim.rs index 02981939..e1cc3a84 100644 --- a/src/mir/join_ir/lowering/generic_case_a/trim.rs +++ b/src/mir/join_ir/lowering/generic_case_a/trim.rs @@ -44,7 +44,6 @@ //! - `value_id_ranges::funcscanner_trim` - ValueId allocation strategy //! - `whitespace_check` - Whitespace detection helper (shared with skip_ws) - use crate::mir::join_ir::lowering::loop_scope_shape::CaseAContext; use crate::mir::join_ir::lowering::value_id_ranges; use crate::mir::join_ir::{ diff --git a/src/mir/join_ir/lowering/generic_case_a/whitespace_check.rs b/src/mir/join_ir/lowering/generic_case_a/whitespace_check.rs index fe1a85f4..d505130d 100644 --- a/src/mir/join_ir/lowering/generic_case_a/whitespace_check.rs +++ b/src/mir/join_ir/lowering/generic_case_a/whitespace_check.rs @@ -59,10 +59,7 @@ impl WhitespaceDetector { /// 具体的な JoinInst 生成は呼び出し側で行う。 /// ここは判定ロジック(どの文字を空白と判定するか)を記録する。 #[allow(dead_code)] - pub fn build_whitespace_check_expr( - ch_value: ValueId, - _debug: bool, - ) -> Option { + pub fn build_whitespace_check_expr(ch_value: ValueId, _debug: bool) -> Option { // NOTE: JoinInst を生成する実装は呼び出し側で行う // ここは判定ロジック(どの文字を空白と判定するか)を記録 diff --git a/src/mir/join_ir/lowering/if_lowering_router.rs b/src/mir/join_ir/lowering/if_lowering_router.rs index 1d786f5b..e57626bd 100644 --- a/src/mir/join_ir/lowering/if_lowering_router.rs +++ b/src/mir/join_ir/lowering/if_lowering_router.rs @@ -133,7 +133,10 @@ pub fn try_lower_if_to_joinir( // IfMerge が成功すればそれを返す、失敗したら Select を試行 // Phase 61-1: context がある場合は with_context() を使用 let if_merge_lowerer = if let Some(ctx) = context { - crate::mir::join_ir::lowering::if_merge::IfMergeLowerer::with_context(debug_level, ctx.clone()) + crate::mir::join_ir::lowering::if_merge::IfMergeLowerer::with_context( + debug_level, + ctx.clone(), + ) } else { crate::mir::join_ir::lowering::if_merge::IfMergeLowerer::new(debug_level) }; @@ -153,7 +156,10 @@ pub fn try_lower_if_to_joinir( // 4. IfMerge が失敗したら Select を試行(単一変数パターン) // Phase 61-1: context がある場合は with_context() を使用 let if_select_lowerer = if let Some(ctx) = context { - crate::mir::join_ir::lowering::if_select::IfSelectLowerer::with_context(debug_level, ctx.clone()) + crate::mir::join_ir::lowering::if_select::IfSelectLowerer::with_context( + debug_level, + ctx.clone(), + ) } else { crate::mir::join_ir::lowering::if_select::IfSelectLowerer::new(debug_level) }; diff --git a/src/mir/join_ir/lowering/inline_boundary.rs b/src/mir/join_ir/lowering/inline_boundary.rs index f5b09618..406bde1d 100644 --- a/src/mir/join_ir/lowering/inline_boundary.rs +++ b/src/mir/join_ir/lowering/inline_boundary.rs @@ -42,8 +42,8 @@ //! ... //! ``` -use crate::mir::ValueId; use super::carrier_info::CarrierRole; +use crate::mir::ValueId; /// Explicit binding between JoinIR exit value and host variable /// @@ -278,11 +278,11 @@ impl JoinInlineBoundary { host_outputs: vec![], exit_bindings: vec![], #[allow(deprecated)] - condition_inputs: vec![], // Phase 171: Default to empty (deprecated) + condition_inputs: vec![], // Phase 171: Default to empty (deprecated) condition_bindings: vec![], // Phase 171-fix: Default to empty - expr_result: None, // Phase 33-14: Default to carrier-only pattern - loop_var_name: None, // Phase 33-16 - carrier_info: None, // Phase 228: Default to None + expr_result: None, // Phase 33-14: Default to carrier-only pattern + loop_var_name: None, // Phase 33-16 + carrier_info: None, // Phase 228: Default to None } } @@ -321,11 +321,11 @@ impl JoinInlineBoundary { host_outputs, exit_bindings: vec![], #[allow(deprecated)] - condition_inputs: vec![], // Phase 171: Default to empty (deprecated) + condition_inputs: vec![], // Phase 171: Default to empty (deprecated) condition_bindings: vec![], // Phase 171-fix: Default to empty - expr_result: None, // Phase 33-14 - loop_var_name: None, // Phase 33-16 - carrier_info: None, // Phase 228 + expr_result: None, // Phase 33-14 + loop_var_name: None, // Phase 33-16 + carrier_info: None, // Phase 228 } } @@ -381,11 +381,11 @@ impl JoinInlineBoundary { host_outputs: vec![], exit_bindings, #[allow(deprecated)] - condition_inputs: vec![], // Phase 171: Default to empty (deprecated) + condition_inputs: vec![], // Phase 171: Default to empty (deprecated) condition_bindings: vec![], // Phase 171-fix: Default to empty - expr_result: None, // Phase 33-14 - loop_var_name: None, // Phase 33-16 - carrier_info: None, // Phase 228 + expr_result: None, // Phase 33-14 + loop_var_name: None, // Phase 33-16 + carrier_info: None, // Phase 228 } } @@ -429,9 +429,9 @@ impl JoinInlineBoundary { #[allow(deprecated)] condition_inputs, condition_bindings: vec![], // Phase 171-fix: Will be populated by new constructor - expr_result: None, // Phase 33-14 - loop_var_name: None, // Phase 33-16 - carrier_info: None, // Phase 228 + expr_result: None, // Phase 33-14 + loop_var_name: None, // Phase 33-16 + carrier_info: None, // Phase 228 } } @@ -479,9 +479,9 @@ impl JoinInlineBoundary { #[allow(deprecated)] condition_inputs, condition_bindings: vec![], // Phase 171-fix: Will be populated by new constructor - expr_result: None, // Phase 33-14 - loop_var_name: None, // Phase 33-16 - carrier_info: None, // Phase 228 + expr_result: None, // Phase 33-14 + loop_var_name: None, // Phase 33-16 + carrier_info: None, // Phase 228 } } @@ -534,11 +534,11 @@ impl JoinInlineBoundary { host_outputs: vec![], exit_bindings: vec![], #[allow(deprecated)] - condition_inputs: vec![], // Deprecated, use condition_bindings instead + condition_inputs: vec![], // Deprecated, use condition_bindings instead condition_bindings, - expr_result: None, // Phase 33-14 + expr_result: None, // Phase 33-14 loop_var_name: None, // Phase 33-16 - carrier_info: None, // Phase 228 + carrier_info: None, // Phase 228 } } } @@ -550,8 +550,8 @@ mod tests { #[test] fn test_boundary_inputs_only() { let boundary = JoinInlineBoundary::new_inputs_only( - vec![ValueId(0)], // JoinIR uses ValueId(0) for loop var - vec![ValueId(4)], // Host has loop var at ValueId(4) + vec![ValueId(0)], // JoinIR uses ValueId(0) for loop var + vec![ValueId(4)], // Host has loop var at ValueId(4) ); assert_eq!(boundary.join_inputs.len(), 1); @@ -560,17 +560,14 @@ mod tests { #[allow(deprecated)] { assert_eq!(boundary.host_outputs.len(), 0); - assert_eq!(boundary.condition_inputs.len(), 0); // Phase 171: Deprecated field + assert_eq!(boundary.condition_inputs.len(), 0); // Phase 171: Deprecated field } - assert_eq!(boundary.condition_bindings.len(), 0); // Phase 171-fix: New field + assert_eq!(boundary.condition_bindings.len(), 0); // Phase 171-fix: New field } #[test] #[should_panic(expected = "join_inputs and host_inputs must have same length")] fn test_boundary_mismatched_inputs() { - JoinInlineBoundary::new_inputs_only( - vec![ValueId(0), ValueId(1)], - vec![ValueId(4)], - ); + JoinInlineBoundary::new_inputs_only(vec![ValueId(0), ValueId(1)], vec![ValueId(4)]); } } diff --git a/src/mir/join_ir/lowering/inline_boundary_builder.rs b/src/mir/join_ir/lowering/inline_boundary_builder.rs index 3b6319ed..1ce5f800 100644 --- a/src/mir/join_ir/lowering/inline_boundary_builder.rs +++ b/src/mir/join_ir/lowering/inline_boundary_builder.rs @@ -24,9 +24,9 @@ //! .build(); //! ``` -use crate::mir::ValueId; use super::condition_to_joinir::ConditionBinding; use super::inline_boundary::{JoinInlineBoundary, LoopExitBinding}; +use crate::mir::ValueId; /// Role of a parameter in JoinIR lowering (Phase 200-A) /// @@ -96,7 +96,7 @@ impl JoinInlineBoundaryBuilder { expr_result: None, loop_var_name: None, carrier_info: None, // Phase 228: Initialize as None - } + }, } } @@ -224,7 +224,8 @@ impl JoinInlineBoundaryBuilder { // Phase 200-B: Add to condition_bindings without PHI // 1. Allocate JoinIR-local ValueId let join_id = ValueId( - (self.boundary.join_inputs.len() + self.boundary.condition_bindings.len()) as u32 + (self.boundary.join_inputs.len() + self.boundary.condition_bindings.len()) + as u32, ); // 2. Create ConditionBinding @@ -257,7 +258,9 @@ impl JoinInlineBoundaryBuilder { /// /// `Some(ValueId)` if the variable exists in condition_bindings, `None` otherwise. pub fn get_condition_binding(&self, name: &str) -> Option { - self.boundary.condition_bindings.iter() + self.boundary + .condition_bindings + .iter() .find(|b| b.name == name) .map(|b| b.join_value) } @@ -363,18 +366,18 @@ mod tests { #[test] fn test_builder_pattern3_style() { // Pattern3 style: Two carriers (i + sum), exit_bindings, loop_var_name - let boundary = JoinInlineBoundaryBuilder::new() - .with_inputs(vec![ValueId(0), ValueId(1)], vec![ValueId(100), ValueId(101)]) - .with_exit_bindings(vec![ - LoopExitBinding { - carrier_name: "sum".to_string(), - join_exit_value: ValueId(18), - host_slot: ValueId(101), - role: CarrierRole::LoopState, - } - ]) + .with_inputs( + vec![ValueId(0), ValueId(1)], + vec![ValueId(100), ValueId(101)], + ) + .with_exit_bindings(vec![LoopExitBinding { + carrier_name: "sum".to_string(), + join_exit_value: ValueId(18), + host_slot: ValueId(101), + role: CarrierRole::LoopState, + }]) .with_loop_var_name(Some("i".to_string())) .build(); @@ -391,8 +394,8 @@ mod tests { // Pattern4 style: Dynamic carrier count, continue support let boundary = JoinInlineBoundaryBuilder::new() .with_inputs( - vec![ValueId(0), ValueId(1), ValueId(2)], // i + 2 carriers - vec![ValueId(100), ValueId(101), ValueId(102)] + vec![ValueId(0), ValueId(1), ValueId(2)], // i + 2 carriers + vec![ValueId(100), ValueId(101), ValueId(102)], ) .with_exit_bindings(vec![ LoopExitBinding { @@ -406,7 +409,7 @@ mod tests { join_exit_value: ValueId(20), host_slot: ValueId(101), role: CarrierRole::LoopState, - } + }, ]) .with_loop_var_name(Some("i".to_string())) .build(); diff --git a/src/mir/join_ir/lowering/loop_body_local_init.rs b/src/mir/join_ir/lowering/loop_body_local_init.rs index 6745c132..32aa8eb7 100644 --- a/src/mir/join_ir/lowering/loop_body_local_init.rs +++ b/src/mir/join_ir/lowering/loop_body_local_init.rs @@ -224,7 +224,11 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { /// /// * `Ok(ValueId)` - JoinIR ValueId of computed result /// * `Err(msg)` - Unsupported expression (Fail-Fast) - fn lower_init_expr(&mut self, expr: &ASTNode, env: &LoopBodyLocalEnv) -> Result { + fn lower_init_expr( + &mut self, + expr: &ASTNode, + env: &LoopBodyLocalEnv, + ) -> Result { match expr { // Constant literal: 42, 0, 1, "string" (use Literal with value) ASTNode::Literal { value, .. } => { @@ -488,7 +492,6 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { Ok(result_id) } - } #[cfg(test)] diff --git a/src/mir/join_ir/lowering/loop_pattern_router.rs b/src/mir/join_ir/lowering/loop_pattern_router.rs index e55f709c..07ae1461 100644 --- a/src/mir/join_ir/lowering/loop_pattern_router.rs +++ b/src/mir/join_ir/lowering/loop_pattern_router.rs @@ -133,25 +133,33 @@ pub fn try_lower_loop_pattern_to_joinir( // Step 3: Route to appropriate lowerer based on pattern match pattern { LoopPatternKind::Pattern4Continue => { - if let Some(inst) = super::loop_patterns::lower_loop_with_continue_to_joinir(loop_form, lowerer) { + if let Some(inst) = + super::loop_patterns::lower_loop_with_continue_to_joinir(loop_form, lowerer) + { eprintln!("[try_lower_loop_pattern] ✅ Pattern 4 (Continue) matched"); return Some(inst); } } LoopPatternKind::Pattern3IfPhi => { - if let Some(inst) = super::loop_patterns::lower_loop_with_conditional_phi_to_joinir(loop_form, lowerer) { + if let Some(inst) = + super::loop_patterns::lower_loop_with_conditional_phi_to_joinir(loop_form, lowerer) + { eprintln!("[try_lower_loop_pattern] ✅ Pattern 3 (If-Else PHI) matched"); return Some(inst); } } LoopPatternKind::Pattern2Break => { - if let Some(inst) = super::loop_patterns::lower_loop_with_break_to_joinir(loop_form, lowerer) { + if let Some(inst) = + super::loop_patterns::lower_loop_with_break_to_joinir(loop_form, lowerer) + { eprintln!("[try_lower_loop_pattern] ✅ Pattern 2 (Break) matched"); return Some(inst); } } LoopPatternKind::Pattern1SimpleWhile => { - if let Some(inst) = super::loop_patterns::lower_simple_while_to_joinir(loop_form, lowerer) { + if let Some(inst) = + super::loop_patterns::lower_simple_while_to_joinir(loop_form, lowerer) + { eprintln!("[try_lower_loop_pattern] ✅ Pattern 1 (Simple While) matched"); return Some(inst); } diff --git a/src/mir/join_ir/lowering/loop_patterns/mod.rs b/src/mir/join_ir/lowering/loop_patterns/mod.rs index 16004ebd..ee5de6a2 100644 --- a/src/mir/join_ir/lowering/loop_patterns/mod.rs +++ b/src/mir/join_ir/lowering/loop_patterns/mod.rs @@ -48,13 +48,13 @@ pub mod simple_while; pub mod with_break; -pub mod with_if_phi; pub mod with_continue; +pub mod with_if_phi; pub use simple_while::lower_simple_while_to_joinir; pub use with_break::lower_loop_with_break_to_joinir; -pub use with_if_phi::lower_loop_with_conditional_phi_to_joinir; pub use with_continue::lower_loop_with_continue_to_joinir; +pub use with_if_phi::lower_loop_with_conditional_phi_to_joinir; // ============================================================================ // Helper Functions (Shared Utilities) @@ -76,7 +76,6 @@ pub use with_continue::lower_loop_with_continue_to_joinir; #[cfg(test)] mod tests { - // ======================================================================== // Pattern 1: Simple While Loop Tests diff --git a/src/mir/join_ir/lowering/loop_scope_shape/case_a_lowering_shape.rs b/src/mir/join_ir/lowering/loop_scope_shape/case_a_lowering_shape.rs index e0d8b73d..a78fcf01 100644 --- a/src/mir/join_ir/lowering/loop_scope_shape/case_a_lowering_shape.rs +++ b/src/mir/join_ir/lowering/loop_scope_shape/case_a_lowering_shape.rs @@ -236,15 +236,17 @@ impl CaseALoweringShape { // Phase 170-C-2b: Use UpdateSummary if available if let Some(ref summary) = features.update_summary { // Single carrier with CounterLike update → StringExamination - if summary.carriers.len() == 1 - && summary.carriers[0].kind == UpdateKind::CounterLike - { + if summary.carriers.len() == 1 && summary.carriers[0].kind == UpdateKind::CounterLike { return CaseALoweringShape::StringExamination; } // Any AccumulationLike carrier → ArrayAccumulation (for single carrier) // or IterationWithAccumulation (for multiple carriers) - if summary.carriers.iter().any(|c| c.kind == UpdateKind::AccumulationLike) { + if summary + .carriers + .iter() + .any(|c| c.kind == UpdateKind::AccumulationLike) + { if carrier_count == 1 { return CaseALoweringShape::ArrayAccumulation; } else { @@ -299,12 +301,14 @@ impl CaseALoweringShape { // Note: carriers is BTreeSet, so each item is already a String let carrier_names: Vec = scope.carriers.iter().cloned().collect(); let update_summary = - crate::mir::join_ir::lowering::loop_update_summary::analyze_loop_updates_by_name(&carrier_names); + crate::mir::join_ir::lowering::loop_update_summary::analyze_loop_updates_by_name( + &carrier_names, + ); // Create stub features (Phase 170-B will use real LoopFeatures) let stub_features = crate::mir::loop_pattern_detection::LoopFeatures { - has_break: false, // Unknown from LoopScopeShape alone - has_continue: false, // Unknown from LoopScopeShape alone + has_break: false, // Unknown from LoopScopeShape alone + has_continue: false, // Unknown from LoopScopeShape alone has_if: false, has_if_else_phi: false, carrier_count, @@ -319,7 +323,10 @@ impl CaseALoweringShape { /// Is this a recognized lowering shape? #[allow(dead_code)] pub fn is_recognized(&self) -> bool { - !matches!(self, CaseALoweringShape::NotCaseA | CaseALoweringShape::Generic) + !matches!( + self, + CaseALoweringShape::NotCaseA | CaseALoweringShape::Generic + ) } /// Get human-readable name for tracing/debugging @@ -340,9 +347,18 @@ mod tests { #[test] fn test_shape_display() { - assert_eq!(CaseALoweringShape::StringExamination.name(), "StringExamination"); - assert_eq!(CaseALoweringShape::ArrayAccumulation.name(), "ArrayAccumulation"); - assert_eq!(CaseALoweringShape::IterationWithAccumulation.name(), "IterationWithAccumulation"); + assert_eq!( + CaseALoweringShape::StringExamination.name(), + "StringExamination" + ); + assert_eq!( + CaseALoweringShape::ArrayAccumulation.name(), + "ArrayAccumulation" + ); + assert_eq!( + CaseALoweringShape::IterationWithAccumulation.name(), + "IterationWithAccumulation" + ); assert_eq!(CaseALoweringShape::Generic.name(), "Generic"); assert_eq!(CaseALoweringShape::NotCaseA.name(), "NotCaseA"); } diff --git a/src/mir/join_ir/lowering/loop_to_join.rs b/src/mir/join_ir/lowering/loop_to_join.rs index 996cf1c8..22d43a77 100644 --- a/src/mir/join_ir/lowering/loop_to_join.rs +++ b/src/mir/join_ir/lowering/loop_to_join.rs @@ -147,7 +147,10 @@ impl LoopToJoinLowerer { } // Phase 33-23: Validator箱に検証を委譲 - if !self.validator.is_supported_case_a(func, ®ion, &exit_edges, &scope) { + if !self + .validator + .is_supported_case_a(func, ®ion, &exit_edges, &scope) + { if self.debug { eprintln!( "[LoopToJoinLowerer] rejected by validator: {:?}", diff --git a/src/mir/join_ir/lowering/loop_update_analyzer.rs b/src/mir/join_ir/lowering/loop_update_analyzer.rs index eb37c4f1..34429445 100644 --- a/src/mir/join_ir/lowering/loop_update_analyzer.rs +++ b/src/mir/join_ir/lowering/loop_update_analyzer.rs @@ -81,7 +81,8 @@ impl LoopUpdateAnalyzer { pub fn analyze_carrier_updates( body: &[ASTNode], carriers: &[CarrierVar], - ) -> BTreeMap { // Phase 222.5-D: HashMap → BTreeMap for determinism + ) -> BTreeMap { + // Phase 222.5-D: HashMap → BTreeMap for determinism let mut updates = BTreeMap::new(); // Extract carrier names for quick lookup @@ -109,7 +110,9 @@ impl LoopUpdateAnalyzer { if let Some(target_name) = Self::extract_variable_name(target) { if carrier_names.contains(&target_name.as_str()) { // This is a carrier update, analyze the RHS - if let Some(update_expr) = Self::analyze_update_value(&target_name, value) { + if let Some(update_expr) = + Self::analyze_update_value(&target_name, value) + { updates.insert(target_name, update_expr); } } @@ -233,9 +236,7 @@ impl LoopUpdateAnalyzer { } => Some(UpdateRhs::StringLiteral(s.clone())), // Variable: sum + i (also handles: result + ch) - ASTNode::Variable { name, .. } => { - Some(UpdateRhs::Variable(name.clone())) - } + ASTNode::Variable { name, .. } => Some(UpdateRhs::Variable(name.clone())), // Phase 190: Number accumulation pattern detection // This is called from analyze_update_value, so we're analyzing the full RHS of an assignment @@ -245,9 +246,9 @@ impl LoopUpdateAnalyzer { // Phase 178: Method call or other complex expression // e.g., result + s.substring(pos, end) - ASTNode::Call { .. } - | ASTNode::MethodCall { .. } - | ASTNode::UnaryOp { .. } => Some(UpdateRhs::Other), + ASTNode::Call { .. } | ASTNode::MethodCall { .. } | ASTNode::UnaryOp { .. } => { + Some(UpdateRhs::Other) + } _ => None, } @@ -617,7 +618,11 @@ mod tests { let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers); // Verify both carriers have updates - assert_eq!(updates.len(), 2, "Should detect updates for both i and result"); + assert_eq!( + updates.len(), + 2, + "Should detect updates for both i and result" + ); // Verify i = i + 1 (Const increment) if let Some(UpdateExpr::BinOp { lhs, op, rhs }) = updates.get("i") { @@ -640,10 +645,16 @@ mod tests { assert_eq!(*base, 10, "NumberAccumulation should use base 10"); assert_eq!(digit_var, "digit_pos", "Should use digit_pos variable"); } else { - panic!("Expected NumberAccumulation for result update, got {:?}", rhs); + panic!( + "Expected NumberAccumulation for result update, got {:?}", + rhs + ); } } else { - panic!("Expected BinOp for result update, got {:?}", updates.get("result")); + panic!( + "Expected BinOp for result update, got {:?}", + updates.get("result") + ); } } } diff --git a/src/mir/join_ir/lowering/loop_update_summary.rs b/src/mir/join_ir/lowering/loop_update_summary.rs index 136b78b7..d4b37d35 100644 --- a/src/mir/join_ir/lowering/loop_update_summary.rs +++ b/src/mir/join_ir/lowering/loop_update_summary.rs @@ -155,7 +155,9 @@ impl LoopUpdateSummary { /// /// Returns a set of variable names that are assigned (LHS) in the loop body. /// This prevents phantom carriers from non-assigned variables. -fn extract_assigned_variables(loop_body: &[crate::ast::ASTNode]) -> std::collections::HashSet { +fn extract_assigned_variables( + loop_body: &[crate::ast::ASTNode], +) -> std::collections::HashSet { use crate::ast::ASTNode; let mut assigned = std::collections::HashSet::new(); @@ -170,7 +172,11 @@ fn extract_assigned_variables(loop_body: &[crate::ast::ASTNode]) -> std::collect visit_node(value, assigned); } // If statement: recurse into then/else branches - ASTNode::If { then_body, else_body, .. } => { + ASTNode::If { + then_body, + else_body, + .. + } => { for stmt in then_body { visit_node(stmt, assigned); } @@ -207,7 +213,12 @@ fn classify_update_kind_from_rhs(rhs: &crate::ast::ASTNode) -> UpdateKind { match rhs { // x = x + 1 → CounterLike // x = x + n → AccumulationLike (where n is not 1) - ASTNode::BinaryOp { operator, left, right, .. } => { + ASTNode::BinaryOp { + operator, + left, + right, + .. + } => { if matches!(operator, BinaryOperator::Add) { // Check if left is self-reference (will be validated by caller) if matches!(left.as_ref(), ASTNode::Variable { .. }) { @@ -215,9 +226,9 @@ fn classify_update_kind_from_rhs(rhs: &crate::ast::ASTNode) -> UpdateKind { if let ASTNode::Literal { value, .. } = right.as_ref() { if let LiteralValue::Integer(n) = value { if *n == 1 { - return UpdateKind::CounterLike; // x = x + 1 + return UpdateKind::CounterLike; // x = x + 1 } else { - return UpdateKind::AccumulationLike; // x = x + n + return UpdateKind::AccumulationLike; // x = x + n } } } else { @@ -251,7 +262,10 @@ fn classify_update_kind_from_rhs(rhs: &crate::ast::ASTNode) -> UpdateKind { /// Phase 219: Extract assignment RHS for a given variable /// /// Returns the RHS expression of the first assignment to `var_name` in loop body. -fn find_assignment_rhs<'a>(var_name: &str, loop_body: &'a [crate::ast::ASTNode]) -> Option<&'a crate::ast::ASTNode> { +fn find_assignment_rhs<'a>( + var_name: &str, + loop_body: &'a [crate::ast::ASTNode], +) -> Option<&'a crate::ast::ASTNode> { use crate::ast::ASTNode; fn visit_node<'a>(var_name: &str, node: &'a ASTNode) -> Option<&'a ASTNode> { @@ -265,7 +279,11 @@ fn find_assignment_rhs<'a>(var_name: &str, loop_body: &'a [crate::ast::ASTNode]) // Recurse into value visit_node(var_name, value) } - ASTNode::If { then_body, else_body, .. } => { + ASTNode::If { + then_body, + else_body, + .. + } => { for stmt in then_body { if let Some(rhs) = visit_node(var_name, stmt) { return Some(rhs); @@ -354,7 +372,7 @@ pub fn analyze_loop_updates_by_name(carrier_names: &[String]) -> LoopUpdateSumma .iter() .map(|name| CarrierUpdateInfo { name: name.clone(), - kind: UpdateKind::AccumulationLike, // Default to accumulation + kind: UpdateKind::AccumulationLike, // Default to accumulation then_expr: None, else_expr: None, }) diff --git a/src/mir/join_ir/lowering/loop_view_builder.rs b/src/mir/join_ir/lowering/loop_view_builder.rs index 7c6bf1ee..95a70065 100644 --- a/src/mir/join_ir/lowering/loop_view_builder.rs +++ b/src/mir/join_ir/lowering/loop_view_builder.rs @@ -61,11 +61,7 @@ impl LoopViewBuilder { /// /// - `Some(JoinModule)`: Lowering成功 /// - `None`: 未サポートパターン(フォールバック経路へ) - pub fn build( - &self, - scope: LoopScopeShape, - func_name: Option<&str>, - ) -> Option { + pub fn build(&self, scope: LoopScopeShape, func_name: Option<&str>) -> Option { let name = func_name.unwrap_or(""); // Phase 188-Impl-1: Pattern 1 (Simple While Loop) detection @@ -139,7 +135,10 @@ impl LoopViewBuilder { &mut join_value_space, ) { if self.debug { - eprintln!("[LoopViewBuilder] Pattern 1 lowering succeeded for {:?}", name); + eprintln!( + "[LoopViewBuilder] Pattern 1 lowering succeeded for {:?}", + name + ); } return Some(result); } @@ -181,7 +180,9 @@ impl LoopViewBuilder { } CaseALoweringShape::IterationWithAccumulation => { if self.debug { - eprintln!("[LoopViewBuilder] Shape: IterationWithAccumulation → stage1 lowerer"); + eprintln!( + "[LoopViewBuilder] Shape: IterationWithAccumulation → stage1 lowerer" + ); } generic_case_a::lower_case_a_stage1_usingresolver_with_scope(scope) } diff --git a/src/mir/join_ir/lowering/loop_with_break_minimal/boundary_builder.rs b/src/mir/join_ir/lowering/loop_with_break_minimal/boundary_builder.rs index c3cdd8c0..9a0cb0a9 100644 --- a/src/mir/join_ir/lowering/loop_with_break_minimal/boundary_builder.rs +++ b/src/mir/join_ir/lowering/loop_with_break_minimal/boundary_builder.rs @@ -9,6 +9,24 @@ pub(crate) fn build_fragment_meta( i_exit: ValueId, carrier_exit_ids: &[ValueId], ) -> JoinFragmentMeta { + let dev_on = env::joinir_dev_enabled(); + + if carrier_exit_ids.len() != carrier_info.carriers.len() { + let msg = format!( + "[joinir/boundary] exit value length mismatch: carriers={} exit_ids={}", + carrier_info.carriers.len(), + carrier_exit_ids.len() + ); + debug_assert!( + carrier_exit_ids.len() == carrier_info.carriers.len(), + "{}", + msg + ); + if dev_on { + panic!("{}", msg); + } + } + let mut exit_values = Vec::new(); exit_values.push((loop_var_name.to_string(), i_exit)); @@ -16,9 +34,9 @@ pub(crate) fn build_fragment_meta( exit_values.push((carrier.name.clone(), carrier_exit_ids[idx])); } - if env::joinir_debug_level() > 0 { + if env::joinir_debug_level() > 0 || dev_on { eprintln!( - "[joinir/boundary_builder] Exit values (loop_var='{}', carriers={}): {:?}", + "[joinir/boundary] Exit values (loop_var='{}', carriers={}): {:?}", loop_var_name, carrier_info.carriers.len(), exit_values diff --git a/src/mir/join_ir/lowering/loop_with_break_minimal/header_break_lowering.rs b/src/mir/join_ir/lowering/loop_with_break_minimal/header_break_lowering.rs index fd9ff4a6..f4fb6444 100644 --- a/src/mir/join_ir/lowering/loop_with_break_minimal/header_break_lowering.rs +++ b/src/mir/join_ir/lowering/loop_with_break_minimal/header_break_lowering.rs @@ -1,15 +1,15 @@ use crate::ast::ASTNode; +use crate::mir::builder::MirBuilder; +use crate::mir::join_ir::lowering::carrier_info::CarrierInfo; +use crate::mir::join_ir::lowering::condition_env::ConditionEnv; use crate::mir::join_ir::lowering::condition_lowering_box::ConditionContext; use crate::mir::join_ir::lowering::condition_to_joinir::lower_condition_to_joinir; use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer}; -use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager; -use crate::mir::join_ir::lowering::condition_env::ConditionEnv; use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv; -use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv; -use crate::mir::join_ir::lowering::carrier_info::CarrierInfo; +use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager; use crate::mir::join_ir::JoinInst; +use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv; use crate::mir::ValueId; -use crate::mir::builder::MirBuilder; /// Build a Pattern2ScopeManager for ExprLowerer paths. fn make_scope_manager<'a>( diff --git a/src/mir/join_ir/lowering/loop_with_continue_minimal.rs b/src/mir/join_ir/lowering/loop_with_continue_minimal.rs index 37fca2ee..37497bf9 100644 --- a/src/mir/join_ir/lowering/loop_with_continue_minimal.rs +++ b/src/mir/join_ir/lowering/loop_with_continue_minimal.rs @@ -62,18 +62,18 @@ use crate::mir::builder::MirBuilder; use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta}; use crate::mir::join_ir::lowering::condition_to_joinir::{lower_condition_to_joinir, ConditionEnv}; use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace; +use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv; // Phase 244 use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; use crate::mir::join_ir::lowering::loop_update_analyzer::{UpdateExpr, UpdateRhs}; -use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv; // Phase 244 -use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv; // Phase 244 use crate::mir::join_ir::{ - BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, - MirLikeInst, UnaryOp, + BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst, + UnaryOp, }; -use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox; use crate::mir::loop_pattern_detection::error_messages::{ - format_unsupported_condition_error, extract_body_local_names, + extract_body_local_names, format_unsupported_condition_error, }; +use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv; // Phase 244 +use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox; use crate::mir::ValueId; use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism @@ -130,15 +130,15 @@ pub(crate) fn lower_loop_with_continue_minimal( // Phase 170-D-impl-3: Validate that loop condition only uses supported variable scopes // LoopConditionScopeBox checks that loop conditions don't reference loop-body-local variables let loop_var_name = carrier_info.loop_var_name.clone(); - let loop_cond_scope = LoopConditionScopeBox::analyze( - &loop_var_name, - &[condition], - Some(&_scope), - ); + let loop_cond_scope = + LoopConditionScopeBox::analyze(&loop_var_name, &[condition], Some(&_scope)); if loop_cond_scope.has_loop_body_local() { let body_local_names = extract_body_local_names(&loop_cond_scope.vars); - return Err(format_unsupported_condition_error("pattern4", &body_local_names)); + return Err(format_unsupported_condition_error( + "pattern4", + &body_local_names, + )); } eprintln!( @@ -152,7 +152,11 @@ pub(crate) fn lower_loop_with_continue_minimal( eprintln!( "[joinir/pattern4] Phase 202-C: Generating JoinIR for {} carriers: {:?}", carrier_count, - carrier_info.carriers.iter().map(|c| &c.name).collect::>() + carrier_info + .carriers + .iter() + .map(|c| &c.name) + .collect::>() ); // ================================================================== @@ -171,7 +175,7 @@ pub(crate) fn lower_loop_with_continue_minimal( for _ in 0..carrier_count { carrier_init_ids.push(join_value_space.alloc_local()); } - let loop_result = join_value_space.alloc_local(); // result from loop_step + let loop_result = join_value_space.alloc_local(); // result from loop_step // loop_step() parameters: [i_param, carrier1_param, carrier2_param, ...] let i_param = join_value_space.alloc_local(); @@ -202,10 +206,12 @@ pub(crate) fn lower_loop_with_continue_minimal( // Phase 169 / Phase 171-fix / Phase 244: Lower condition using ConditionLoweringBox trait let (cond_value, mut cond_instructions) = { - use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer}; - use crate::mir::join_ir::lowering::condition_lowering_box::{ConditionLoweringBox, ConditionContext}; - use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager; use crate::mir::builder::MirBuilder; + use crate::mir::join_ir::lowering::condition_lowering_box::{ + ConditionContext, ConditionLoweringBox, + }; + use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer}; + use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager; // Build minimal ScopeManager for header condition let empty_body_env = LoopBodyLocalEnv::new(); @@ -221,7 +227,8 @@ pub(crate) fn lower_loop_with_continue_minimal( if ExprLowerer::::is_supported_condition(condition) { // Phase 244: ExprLowerer via ConditionLoweringBox trait let mut dummy_builder = MirBuilder::new(); - let mut expr_lowerer = ExprLowerer::new(&scope_manager, ExprContext::Condition, &mut dummy_builder); + let mut expr_lowerer = + ExprLowerer::new(&scope_manager, ExprContext::Condition, &mut dummy_builder); let context = ConditionContext { loop_var_name: loop_var_name.clone(), @@ -263,7 +270,7 @@ pub(crate) fn lower_loop_with_continue_minimal( let mut carrier_next_ids: Vec = Vec::new(); let mut carrier_merged_ids: Vec = Vec::new(); for _ in 0..carrier_count { - carrier_next_ids.push(alloc_value()); // carrier_next = carrier + ... + carrier_next_ids.push(alloc_value()); // carrier_next = carrier + ... carrier_merged_ids.push(alloc_value()); // carrier_merged = Select(...) } @@ -303,11 +310,8 @@ pub(crate) fn lower_loop_with_continue_minimal( let mut loop_step_params = vec![i_param]; loop_step_params.extend(carrier_param_ids.iter().copied()); - let mut loop_step_func = JoinFunction::new( - loop_step_id, - "loop_step".to_string(), - loop_step_params, - ); + let mut loop_step_func = + JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_step_params); // ------------------------------------------------------------------ // Natural Exit Condition Check (Phase 169: from AST) @@ -408,9 +412,15 @@ pub(crate) fn lower_loop_with_continue_minimal( let carrier_name = &carrier_info.carriers[idx].name; // Phase 197: Extract RHS from update expression metadata - eprintln!("[loop_with_continue_minimal] Processing carrier '{}' (idx={})", carrier_name, idx); + eprintln!( + "[loop_with_continue_minimal] Processing carrier '{}' (idx={})", + carrier_name, idx + ); let rhs = if let Some(update_expr) = carrier_updates.get(carrier_name) { - eprintln!("[loop_with_continue_minimal] Found update expr: {:?}", update_expr); + eprintln!( + "[loop_with_continue_minimal] Found update expr: {:?}", + update_expr + ); match update_expr { UpdateExpr::BinOp { op, rhs, .. } => { // Verify operator is Add (only supported for now) @@ -451,12 +461,12 @@ pub(crate) fn lower_loop_with_continue_minimal( // This is effectively a passthrough (no JoinIR update) loop_step_func.body.push(JoinInst::Select { dst: carrier_merged, - cond: continue_cond, // Condition doesn't matter when both values are same + cond: continue_cond, // Condition doesn't matter when both values are same then_val: carrier_param, else_val: carrier_param, type_hint: None, }); - continue; // Skip the BinOp and normal Select below + continue; // Skip the BinOp and normal Select below } // Phase 178: String updates detected but not lowered to JoinIR yet // Skip JoinIR update - use Select passthrough to keep carrier_merged defined @@ -469,12 +479,12 @@ pub(crate) fn lower_loop_with_continue_minimal( // This is effectively a passthrough (no JoinIR update) loop_step_func.body.push(JoinInst::Select { dst: carrier_merged, - cond: continue_cond, // Condition doesn't matter when both values are same + cond: continue_cond, // Condition doesn't matter when both values are same then_val: carrier_param, else_val: carrier_param, type_hint: None, }); - continue; // Skip the BinOp and normal Select below + continue; // Skip the BinOp and normal Select below } } } @@ -495,8 +505,10 @@ pub(crate) fn lower_loop_with_continue_minimal( }; // carrier_next = carrier_param + rhs - eprintln!("[loop_with_continue_minimal] Generating: ValueId({}) = ValueId({}) + ValueId({})", - carrier_next.0, carrier_param.0, rhs.0); + eprintln!( + "[loop_with_continue_minimal] Generating: ValueId({}) = ValueId({}) + ValueId({})", + carrier_next.0, carrier_param.0, rhs.0 + ); loop_step_func .body .push(JoinInst::Compute(MirLikeInst::BinOp { @@ -510,8 +522,8 @@ pub(crate) fn lower_loop_with_continue_minimal( loop_step_func.body.push(JoinInst::Select { dst: carrier_merged, cond: continue_cond, - then_val: carrier_param, // Continue: no update - else_val: carrier_next, // Normal: update + then_val: carrier_param, // Continue: no update + else_val: carrier_next, // Normal: update type_hint: None, }); } @@ -525,7 +537,7 @@ pub(crate) fn lower_loop_with_continue_minimal( loop_step_func.body.push(JoinInst::Call { func: loop_step_id, args: tail_call_args, - k_next: None, // CRITICAL: None for tail call + k_next: None, // CRITICAL: None for tail call dst: None, }); @@ -534,11 +546,8 @@ pub(crate) fn lower_loop_with_continue_minimal( // ================================================================== // k_exit(carrier1_exit, carrier2_exit, ...) function // ================================================================== - let mut k_exit_func = JoinFunction::new( - k_exit_id, - "k_exit".to_string(), - carrier_exit_ids.clone(), - ); + let mut k_exit_func = + JoinFunction::new(k_exit_id, "k_exit".to_string(), carrier_exit_ids.clone()); // For now, return the first carrier's exit value (or void if no carriers) // TODO: Consider returning a tuple or using a different mechanism @@ -562,7 +571,11 @@ pub(crate) fn lower_loop_with_continue_minimal( eprintln!( "[joinir/pattern4] Carriers: {} ({:?})", carrier_count, - carrier_info.carriers.iter().map(|c| c.name.as_str()).collect::>() + carrier_info + .carriers + .iter() + .map(|c| c.name.as_str()) + .collect::>() ); // ================================================================== diff --git a/src/mir/join_ir/lowering/loop_with_if_phi_if_sum.rs b/src/mir/join_ir/lowering/loop_with_if_phi_if_sum.rs index 1f168077..5589ce4e 100644 --- a/src/mir/join_ir/lowering/loop_with_if_phi_if_sum.rs +++ b/src/mir/join_ir/lowering/loop_with_if_phi_if_sum.rs @@ -32,14 +32,16 @@ use crate::ast::ASTNode; use crate::mir::join_ir::lowering::carrier_info::{ExitMeta, JoinFragmentMeta}; use crate::mir::join_ir::lowering::condition_env::ConditionEnv; use crate::mir::join_ir::lowering::condition_lowerer::lower_value_expression; -use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace; #[cfg(debug_assertions)] -use crate::mir::join_ir::lowering::condition_pattern::{analyze_condition_pattern, ConditionPattern}; -use crate::mir::ValueId; -use crate::mir::join_ir::{ - BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, - MirLikeInst, UnaryOp, +use crate::mir::join_ir::lowering::condition_pattern::{ + analyze_condition_pattern, ConditionPattern, }; +use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace; +use crate::mir::join_ir::{ + BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst, + UnaryOp, +}; +use crate::mir::ValueId; /// Phase 213: Lower if-sum pattern to JoinIR using AST /// @@ -83,22 +85,34 @@ pub fn lower_if_sum_pattern( // Uses cond_env for variable resolution (e.g., `len` in `i < len`) let (loop_var, loop_op, loop_lhs_val, loop_limit_val, loop_limit_insts) = extract_loop_condition(loop_condition, &mut alloc_value, cond_env)?; - eprintln!("[joinir/pattern3/if-sum] Loop condition: {} {:?} ValueId({})", loop_var, loop_op, loop_limit_val.0); + eprintln!( + "[joinir/pattern3/if-sum] Loop condition: {} {:?} ValueId({})", + loop_var, loop_op, loop_limit_val.0 + ); // Step 2: Extract if condition info (e.g., i > 0 → var="i", op=Gt, value=ValueId) // Phase 220-D: Now returns ValueId and instructions // Phase 242-EX-A: Now supports complex LHS let (if_var, if_op, if_lhs_val, if_value_val, if_value_insts) = extract_if_condition(if_stmt, &mut alloc_value, cond_env)?; - eprintln!("[joinir/pattern3/if-sum] If condition: {} {:?} ValueId({})", if_var, if_op, if_value_val.0); + eprintln!( + "[joinir/pattern3/if-sum] If condition: {} {:?} ValueId({})", + if_var, if_op, if_value_val.0 + ); // Step 3: Extract then-branch update (e.g., sum = sum + 1 → var="sum", addend=1) let (update_var, update_addend) = extract_then_update(if_stmt)?; - eprintln!("[joinir/pattern3/if-sum] Then update: {} += {}", update_var, update_addend); + eprintln!( + "[joinir/pattern3/if-sum] Then update: {} += {}", + update_var, update_addend + ); // Step 4: Extract counter update (e.g., i = i + 1 → var="i", step=1) let (counter_var, counter_step) = extract_counter_update(body, &loop_var)?; - eprintln!("[joinir/pattern3/if-sum] Counter update: {} += {}", counter_var, counter_step); + eprintln!( + "[joinir/pattern3/if-sum] Counter update: {} += {}", + counter_var, counter_step + ); // Step 5: Generate JoinIR let mut alloc_value = || join_value_space.alloc_local(); @@ -111,10 +125,10 @@ pub fn lower_if_sum_pattern( // === ValueId allocation === // main() locals - let i_init_val = alloc_value(); // i = 0 - let sum_init_val = alloc_value(); // sum = 0 + let i_init_val = alloc_value(); // i = 0 + let sum_init_val = alloc_value(); // sum = 0 let count_init_val = alloc_value(); // count = 0 (optional) - let loop_result = alloc_value(); // result from loop_step + let loop_result = alloc_value(); // result from loop_step // loop_step params let i_param = alloc_value(); @@ -124,19 +138,19 @@ pub fn lower_if_sum_pattern( // loop_step locals // Phase 220-D: loop_limit_val and if_value_val are already allocated by extract_*_condition() // and will be used directly from their return values - let cmp_loop = alloc_value(); // loop condition comparison - let exit_cond = alloc_value(); // negated loop condition - let if_cmp = alloc_value(); // if condition comparison - let sum_then = alloc_value(); // sum + update_addend - let count_const = alloc_value(); // count increment (1) - let count_then = alloc_value(); // count + 1 - let const_0 = alloc_value(); // 0 for else branch - let sum_else = alloc_value(); // sum + 0 (identity) - let count_else = alloc_value(); // count + 0 (identity) - let sum_new = alloc_value(); // Select result for sum - let count_new = alloc_value(); // Select result for count - let step_const = alloc_value(); // counter step - let i_next = alloc_value(); // i + step + let cmp_loop = alloc_value(); // loop condition comparison + let exit_cond = alloc_value(); // negated loop condition + let if_cmp = alloc_value(); // if condition comparison + let sum_then = alloc_value(); // sum + update_addend + let count_const = alloc_value(); // count increment (1) + let count_then = alloc_value(); // count + 1 + let const_0 = alloc_value(); // 0 for else branch + let sum_else = alloc_value(); // sum + 0 (identity) + let count_else = alloc_value(); // count + 0 (identity) + let sum_new = alloc_value(); // Select result for sum + let count_new = alloc_value(); // Select result for count + let step_const = alloc_value(); // counter step + let i_next = alloc_value(); // i + step // k_exit params let sum_final = alloc_value(); @@ -148,7 +162,7 @@ pub fn lower_if_sum_pattern( // i_init = 0 (initial value from ctx) main_func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: i_init_val, - value: ConstValue::Integer(0), // TODO: Get from AST + value: ConstValue::Integer(0), // TODO: Get from AST })); // sum_init = 0 @@ -195,19 +209,23 @@ pub fn lower_if_sum_pattern( // Compare: i < limit (or other op from AST) // Phase 242-EX-A: Use computed LHS if available, otherwise use loop parameter let loop_lhs = loop_lhs_val.unwrap_or(i_param); - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare { - dst: cmp_loop, - op: loop_op, - lhs: loop_lhs, - rhs: loop_limit_val, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_loop, + op: loop_op, + lhs: loop_lhs, + rhs: loop_limit_val, + })); // exit_cond = !cmp_loop - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::UnaryOp { - dst: exit_cond, - op: UnaryOp::Not, - operand: cmp_loop, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::UnaryOp { + dst: exit_cond, + op: UnaryOp::Not, + operand: cmp_loop, + })); // Jump to exit if condition is false loop_step_func.body.push(JoinInst::Jump { @@ -226,86 +244,110 @@ pub fn lower_if_sum_pattern( // Compare: if_var if_value // Phase 242-EX-A: Use computed LHS if available, otherwise use loop parameter let if_lhs = if_lhs_val.unwrap_or(i_param); - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare { - dst: if_cmp, - op: if_op, - lhs: if_lhs, - rhs: if_value_val, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: if_cmp, + op: if_op, + lhs: if_lhs, + rhs: if_value_val, + })); // --- Then Branch --- // sum_then = sum + update_addend - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const { - dst: const_0, - value: ConstValue::Integer(update_addend), - })); - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { - dst: sum_then, - op: BinOpKind::Add, - lhs: sum_param, - rhs: const_0, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_0, + value: ConstValue::Integer(update_addend), + })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: sum_then, + op: BinOpKind::Add, + lhs: sum_param, + rhs: const_0, + })); // count_then = count + 1 - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const { - dst: count_const, - value: ConstValue::Integer(1), - })); - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { - dst: count_then, - op: BinOpKind::Add, - lhs: count_param, - rhs: count_const, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: count_const, + value: ConstValue::Integer(1), + })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: count_then, + op: BinOpKind::Add, + lhs: count_param, + rhs: count_const, + })); // --- Else Branch --- // sum_else = sum + 0 (identity) - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const { - dst: step_const, // reuse for 0 - value: ConstValue::Integer(0), - })); - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { - dst: sum_else, - op: BinOpKind::Add, - lhs: sum_param, - rhs: step_const, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: step_const, // reuse for 0 + value: ConstValue::Integer(0), + })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: sum_else, + op: BinOpKind::Add, + lhs: sum_param, + rhs: step_const, + })); // count_else = count + 0 (identity) - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { - dst: count_else, - op: BinOpKind::Add, - lhs: count_param, - rhs: step_const, // 0 - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: count_else, + op: BinOpKind::Add, + lhs: count_param, + rhs: step_const, // 0 + })); // --- Select --- - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Select { - dst: sum_new, - cond: if_cmp, - then_val: sum_then, - else_val: sum_else, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Select { + dst: sum_new, + cond: if_cmp, + then_val: sum_then, + else_val: sum_else, + })); - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Select { - dst: count_new, - cond: if_cmp, - then_val: count_then, - else_val: count_else, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Select { + dst: count_new, + cond: if_cmp, + then_val: count_then, + else_val: count_else, + })); // --- Counter Update --- let step_const2 = alloc_value(); - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const { - dst: step_const2, - value: ConstValue::Integer(counter_step), - })); - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { - dst: i_next, - op: BinOpKind::Add, - lhs: i_param, - rhs: step_const2, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: step_const2, + value: ConstValue::Integer(counter_step), + })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: i_next, + op: BinOpKind::Add, + lhs: i_param, + rhs: step_const2, + })); // --- Tail Recursion --- loop_step_func.body.push(JoinInst::Call { @@ -343,9 +385,18 @@ pub fn lower_if_sum_pattern( let fragment_meta = JoinFragmentMeta::with_expr_result(sum_final, exit_meta); eprintln!("[joinir/pattern3/if-sum] Generated AST-based JoinIR"); - eprintln!("[joinir/pattern3/if-sum] Loop: {} {:?} ValueId({})", loop_var, loop_op, loop_limit_val.0); - eprintln!("[joinir/pattern3/if-sum] If: {} {:?} ValueId({})", if_var, if_op, if_value_val.0); - eprintln!("[joinir/pattern3/if-sum] Phase 215-2: expr_result={:?}", sum_final); + eprintln!( + "[joinir/pattern3/if-sum] Loop: {} {:?} ValueId({})", + loop_var, loop_op, loop_limit_val.0 + ); + eprintln!( + "[joinir/pattern3/if-sum] If: {} {:?} ValueId({})", + if_var, if_op, if_value_val.0 + ); + eprintln!( + "[joinir/pattern3/if-sum] Phase 215-2: expr_result={:?}", + sum_final + ); Ok((join_module, fragment_meta)) } @@ -406,7 +457,12 @@ where ConditionValue::Variable(var_name) => { let var_node = ASTNode::Variable { name: var_name, - span: crate::ast::Span { start: 0, end: 0, line: 1, column: 1 }, + span: crate::ast::Span { + start: 0, + end: 0, + line: 1, + column: 1, + }, }; lower_value_expression(&var_node, alloc_value, cond_env, &mut limit_instructions)? } @@ -418,7 +474,12 @@ where // Phase 242-EX-A: Normalization failed → handle complex conditions dynamically // Support: `expr CmpOp expr` (e.g., `i % 2 == 1`, `a + b > c`) match cond { - ASTNode::BinaryOp { operator, left, right, .. } => { + ASTNode::BinaryOp { + operator, + left, + right, + .. + } => { use crate::ast::BinaryOperator; // Convert operator to CompareOp @@ -429,7 +490,12 @@ where BinaryOperator::GreaterEqual => CompareOp::Ge, BinaryOperator::Equal => CompareOp::Eq, BinaryOperator::NotEqual => CompareOp::Ne, - _ => return Err(format!("[if-sum] Unsupported operator in condition: {:?}", operator)), + _ => { + return Err(format!( + "[if-sum] Unsupported operator in condition: {:?}", + operator + )) + } }; // Lower left-hand side (complex expression) @@ -473,7 +539,7 @@ where { match if_stmt { ASTNode::If { condition, .. } => { - extract_loop_condition(condition, alloc_value, cond_env) // Same format + extract_loop_condition(condition, alloc_value, cond_env) // Same format } _ => Err("[if-sum] Expected If statement".to_string()), } @@ -490,7 +556,13 @@ fn extract_then_update(if_stmt: &ASTNode) -> Result<(String, i64), String> { if let ASTNode::Assignment { target, value, .. } = stmt { let target_name = extract_variable_name(&**target)?; // Check if value is var + lit - if let ASTNode::BinaryOp { operator: crate::ast::BinaryOperator::Add, left, right, .. } = value.as_ref() { + if let ASTNode::BinaryOp { + operator: crate::ast::BinaryOperator::Add, + left, + right, + .. + } = value.as_ref() + { let lhs_name = extract_variable_name(left)?; if lhs_name == target_name { let addend = extract_integer_literal(right)?; @@ -513,7 +585,13 @@ fn extract_counter_update(body: &[ASTNode], loop_var: &str) -> Result<(String, i if let ASTNode::Assignment { target, value, .. } = stmt { if let Ok(target_name) = extract_variable_name(&**target) { if target_name == loop_var { - if let ASTNode::BinaryOp { operator: crate::ast::BinaryOperator::Add, left, right, .. } = value.as_ref() { + if let ASTNode::BinaryOp { + operator: crate::ast::BinaryOperator::Add, + left, + right, + .. + } = value.as_ref() + { let lhs_name = extract_variable_name(left)?; if lhs_name == target_name { let step = extract_integer_literal(right)?; @@ -524,7 +602,10 @@ fn extract_counter_update(body: &[ASTNode], loop_var: &str) -> Result<(String, i } } } - Err(format!("[if-sum] No counter update found for '{}'", loop_var)) + Err(format!( + "[if-sum] No counter update found for '{}'", + loop_var + )) } /// Extract variable name from AST node @@ -541,7 +622,10 @@ fn extract_variable_name(node: &ASTNode) -> Result { /// For condition values, use `lower_value_expression()` which supports variables. fn extract_integer_literal(node: &ASTNode) -> Result { match node { - ASTNode::Literal { value: crate::ast::LiteralValue::Integer(n), .. } => Ok(*n), + ASTNode::Literal { + value: crate::ast::LiteralValue::Integer(n), + .. + } => Ok(*n), _ => Err(format!("[if-sum] Expected integer literal, got {:?}", node)), } } @@ -602,14 +686,8 @@ mod tests { int_lit(1), ); - let sum_update = assignment( - var("sum"), - bin(BinaryOperator::Add, var("sum"), int_lit(1)), - ); - let counter_update = assignment( - var("i"), - bin(BinaryOperator::Add, var("i"), int_lit(1)), - ); + let sum_update = assignment(var("sum"), bin(BinaryOperator::Add, var("sum"), int_lit(1))); + let counter_update = assignment(var("i"), bin(BinaryOperator::Add, var("i"), int_lit(1))); let if_stmt = ASTNode::If { condition: Box::new(if_condition), @@ -634,7 +712,9 @@ mod tests { for func in module.functions.values() { for inst in &func.body { match inst { - JoinInst::Compute(MirLikeInst::BinOp { op: BinOpKind::Mod, .. }) => { + JoinInst::Compute(MirLikeInst::BinOp { + op: BinOpKind::Mod, .. + }) => { has_mod = true; } JoinInst::Compute(MirLikeInst::Compare { .. }) => { diff --git a/src/mir/join_ir/lowering/method_call_lowerer.rs b/src/mir/join_ir/lowering/method_call_lowerer.rs index 5f52e911..536c061d 100644 --- a/src/mir/join_ir/lowering/method_call_lowerer.rs +++ b/src/mir/join_ir/lowering/method_call_lowerer.rs @@ -116,7 +116,10 @@ impl MethodCallLowerer { if args.len() != expected_arity { return Err(format!( "Arity mismatch: {}.{}() expects {} args, got {}", - recv_val.0, method_name, expected_arity, args.len() + recv_val.0, + method_name, + expected_arity, + args.len() )); } @@ -127,7 +130,7 @@ impl MethodCallLowerer { arg_ast, alloc_value, env, - instructions + instructions, )?; lowered_args.push(arg_val); } @@ -201,7 +204,10 @@ impl MethodCallLowerer { if args.len() != expected_arity { return Err(format!( "Arity mismatch: {}.{}() expects {} args, got {}", - recv_val.0, method_name, expected_arity, args.len() + recv_val.0, + method_name, + expected_arity, + args.len() )); } @@ -214,7 +220,7 @@ impl MethodCallLowerer { alloc_value, cond_env, body_local_env, - instructions + instructions, )?; lowered_args.push(arg_val); } @@ -311,8 +317,8 @@ impl MethodCallLowerer { #[cfg(test)] mod tests { use super::*; - use crate::mir::join_ir::JoinInst; use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv; + use crate::mir::join_ir::JoinInst; #[test] fn test_resolve_string_length() { @@ -391,7 +397,9 @@ mod tests { ); assert!(result.is_err()); - assert!(result.unwrap_err().contains("not allowed in loop condition")); + assert!(result + .unwrap_err() + .contains("not allowed in loop condition")); } #[test] @@ -461,7 +469,9 @@ mod tests { &mut instructions, ); assert!(cond_result.is_err()); - assert!(cond_result.unwrap_err().contains("not allowed in loop condition")); + assert!(cond_result + .unwrap_err() + .contains("not allowed in loop condition")); // But IS allowed in init context // Phase 226: Create empty LoopBodyLocalEnv diff --git a/src/mir/join_ir/lowering/mod.rs b/src/mir/join_ir/lowering/mod.rs index 08ddf137..dd3f6428 100644 --- a/src/mir/join_ir/lowering/mod.rs +++ b/src/mir/join_ir/lowering/mod.rs @@ -24,26 +24,20 @@ pub mod carrier_info; // Phase 196: Carrier metadata for loop lowering pub(crate) mod carrier_update_emitter; // Phase 179: Carrier update instruction emission pub(crate) mod common; // Internal lowering utilities pub mod complex_addend_normalizer; // Phase 192: Complex addend normalization (AST preprocessing) -pub mod digitpos_condition_normalizer; // Phase 224-E: DigitPos condition normalizer (digit_pos < 0 → !is_digit_pos) pub mod condition_env; // Phase 171-fix: Condition expression environment +pub(crate) mod condition_lowerer; // Phase 171-fix: Core condition lowering logic pub mod condition_lowering_box; // Phase 244: Unified condition lowering interface (trait-based) pub mod condition_pattern; // Phase 219-fix: If condition pattern detection (simple vs complex) -pub mod loop_body_local_env; // Phase 184: Body-local variable environment -pub mod loop_body_local_init; // Phase 186: Body-local init expression lowering -pub(crate) mod condition_lowerer; // Phase 171-fix: Core condition lowering logic pub mod condition_to_joinir; // Phase 169: JoinIR condition lowering orchestrator (refactored) -pub mod method_call_lowerer; // Phase 224-B: MethodCall lowering (metadata-driven) pub(crate) mod condition_var_extractor; // Phase 171-fix: Variable extraction from condition AST pub mod continue_branch_normalizer; // Phase 33-19: Continue branch normalization for Pattern B -pub mod expr_lowerer; // Phase 231: Unified expression lowering with scope management -pub mod loop_update_analyzer; // Phase 197: Update expression analyzer for carrier semantics -pub mod loop_update_summary; // Phase 170-C-2: Update pattern summary for shape detection +pub mod digitpos_condition_normalizer; // Phase 224-E: DigitPos condition normalizer (digit_pos < 0 → !is_digit_pos) pub(crate) mod exit_args_resolver; // Internal exit argument resolution +pub mod expr_lowerer; // Phase 231: Unified expression lowering with scope management pub mod funcscanner_append_defs; pub mod funcscanner_trim; pub(crate) mod generic_case_a; // Phase 192: Modularized Case A lowering pub mod generic_type_resolver; // Phase 66: P3-C ジェネリック型推論箱 -pub mod method_return_hint; // Phase 83: P3-D 既知メソッド戻り値型推論箱 pub mod if_dry_runner; // Phase 33-10.0 pub(crate) mod if_lowering_router; // Phase 33-12: If-expression routing (re-exported) pub mod if_merge; // Phase 33-7 @@ -53,20 +47,26 @@ pub(crate) mod if_select; // Phase 33: Internal If/Select lowering pub mod inline_boundary; // Phase 188-Impl-3: JoinIR→Host boundary pub mod inline_boundary_builder; // Phase 200-2: Builder pattern for JoinInlineBoundary pub mod join_value_space; // Phase 201: Unified JoinIR ValueId allocation +pub mod loop_body_local_env; // Phase 184: Body-local variable environment +pub mod loop_body_local_init; // Phase 186: Body-local init expression lowering pub(crate) mod loop_form_intake; // Internal loop form intake -pub mod scope_manager; // Phase 231: Unified variable scope management pub(crate) mod loop_pattern_router; // Phase 33-12: Loop pattern routing (re-exported) pub(crate) mod loop_pattern_validator; // Phase 33-23: Loop structure validation pub(crate) mod loop_patterns; // Phase 188: Pattern-based loop lowering (3 patterns) pub mod loop_scope_shape; pub mod loop_to_join; +pub mod loop_update_analyzer; // Phase 197: Update expression analyzer for carrier semantics +pub mod loop_update_summary; // Phase 170-C-2: Update pattern summary for shape detection pub(crate) mod loop_view_builder; // Phase 33-23: Loop lowering dispatch pub mod loop_with_break_minimal; // Phase 188-Impl-2: Pattern 2 minimal lowerer -pub mod loop_with_continue_minimal; // Phase 195: Pattern 4 minimal lowerer -// Phase 242-EX-A: loop_with_if_phi_minimal removed - replaced by loop_with_if_phi_if_sum +pub mod loop_with_continue_minimal; +pub mod method_call_lowerer; // Phase 224-B: MethodCall lowering (metadata-driven) +pub mod method_return_hint; // Phase 83: P3-D 既知メソッド戻り値型推論箱 +pub mod scope_manager; // Phase 231: Unified variable scope management // Phase 195: Pattern 4 minimal lowerer + // Phase 242-EX-A: loop_with_if_phi_minimal removed - replaced by loop_with_if_phi_if_sum pub mod loop_with_if_phi_if_sum; // Phase 213: Pattern 3 AST-based if-sum lowerer (Phase 242-EX-A: supports complex conditions) -pub mod simple_while_minimal; // Phase 188-Impl-1: Pattern 1 minimal lowerer pub mod min_loop; +pub mod simple_while_minimal; // Phase 188-Impl-1: Pattern 1 minimal lowerer pub mod skip_ws; pub mod stage1_using_resolver; pub mod stageb_body; diff --git a/src/mir/join_ir/lowering/scope_manager.rs b/src/mir/join_ir/lowering/scope_manager.rs index 4560b55b..658a4f8d 100644 --- a/src/mir/join_ir/lowering/scope_manager.rs +++ b/src/mir/join_ir/lowering/scope_manager.rs @@ -19,11 +19,11 @@ //! Phase 231 starts with Pattern2-specific implementation to validate the design. //! Future phases will generalize to Pattern1, Pattern3, etc. -use crate::mir::ValueId; +use super::carrier_info::CarrierInfo; use super::condition_env::ConditionEnv; use super::loop_body_local_env::LoopBodyLocalEnv; -use super::carrier_info::CarrierInfo; use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv; +use crate::mir::ValueId; /// Phase 231: Scope kind for variables /// @@ -146,8 +146,7 @@ impl<'a> ScopeManager for Pattern2ScopeManager<'a> { } // 4. Promoted LoopBodyLocal → Carrier lookup(命名規約は CarrierInfo 側に集約) - self.carrier_info - .resolve_promoted_join_id(name) + self.carrier_info.resolve_promoted_join_id(name) } fn scope_of(&self, name: &str) -> Option { @@ -182,7 +181,7 @@ impl<'a> ScopeManager for Pattern2ScopeManager<'a> { #[cfg(test)] mod tests { use super::*; - use crate::mir::join_ir::lowering::carrier_info::{CarrierVar, CarrierRole, CarrierInit}; + use crate::mir::join_ir::lowering::carrier_info::{CarrierInit, CarrierRole, CarrierVar}; use crate::mir::loop_pattern_detection::function_scope_capture::CapturedVar; #[test] @@ -218,15 +217,13 @@ mod tests { let carrier_info = CarrierInfo { loop_var_name: "i".to_string(), loop_var_id: ValueId(1), - carriers: vec![ - CarrierVar { - name: "sum".to_string(), - host_id: ValueId(2), - join_id: Some(ValueId(101)), - role: CarrierRole::LoopState, - init: CarrierInit::FromHost, - }, - ], + carriers: vec![CarrierVar { + name: "sum".to_string(), + host_id: ValueId(2), + join_id: Some(ValueId(101)), + role: CarrierRole::LoopState, + init: CarrierInit::FromHost, + }], trim_helper: None, promoted_loopbodylocals: vec![], }; @@ -250,15 +247,13 @@ mod tests { let carrier_info = CarrierInfo { loop_var_name: "i".to_string(), loop_var_id: ValueId(1), - carriers: vec![ - CarrierVar { - name: "is_digit_pos".to_string(), - host_id: ValueId(2), - join_id: Some(ValueId(102)), - role: CarrierRole::ConditionOnly, - init: CarrierInit::BoolConst(false), - }, - ], + carriers: vec![CarrierVar { + name: "is_digit_pos".to_string(), + host_id: ValueId(2), + join_id: Some(ValueId(102)), + role: CarrierRole::ConditionOnly, + init: CarrierInit::BoolConst(false), + }], trim_helper: None, promoted_loopbodylocals: vec!["digit_pos".to_string()], }; @@ -307,7 +302,8 @@ mod tests { condition_env.insert("i".to_string(), ValueId(100)); condition_env.insert("len".to_string(), ValueId(201)); - let mut captured_env = crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv::new(); + let mut captured_env = + crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv::new(); captured_env.add_var(CapturedVar { name: "len".to_string(), host_id: ValueId(42), diff --git a/src/mir/join_ir/lowering/simple_while_minimal.rs b/src/mir/join_ir/lowering/simple_while_minimal.rs index c7804688..30eeee43 100644 --- a/src/mir/join_ir/lowering/simple_while_minimal.rs +++ b/src/mir/join_ir/lowering/simple_while_minimal.rs @@ -45,11 +45,10 @@ use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace; use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; use crate::mir::join_ir::{ - BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, - MirLikeInst, UnaryOp, + BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst, + UnaryOp, }; - /// Lower Pattern 1 (Simple While Loop) to JoinIR /// /// # Phase 188-Impl-3: Pure JoinIR Fragment Generation @@ -106,17 +105,17 @@ pub(crate) fn lower_simple_while_minimal( // ValueId allocation (Phase 188-Impl-3: Sequential local IDs) // ================================================================== // main() locals - let i_init = alloc_value(); // ValueId(0) - loop init value - let loop_result = alloc_value(); // ValueId(1) - result from loop_step + let i_init = alloc_value(); // ValueId(0) - loop init value + let loop_result = alloc_value(); // ValueId(1) - result from loop_step let const_0_main = alloc_value(); // ValueId(2) - return value // loop_step locals - let i_param = alloc_value(); // ValueId(3) - parameter - let const_3 = alloc_value(); // ValueId(4) - comparison constant - let cmp_lt = alloc_value(); // ValueId(5) - i < 3 - let exit_cond = alloc_value(); // ValueId(6) - !(i < 3) - let const_1 = alloc_value(); // ValueId(7) - increment constant - let i_next = alloc_value(); // ValueId(8) - i + 1 + let i_param = alloc_value(); // ValueId(3) - parameter + let const_3 = alloc_value(); // ValueId(4) - comparison constant + let cmp_lt = alloc_value(); // ValueId(5) - i < 3 + let exit_cond = alloc_value(); // ValueId(6) - !(i < 3) + let const_1 = alloc_value(); // ValueId(7) - increment constant + let i_next = alloc_value(); // ValueId(8) - i + 1 // k_exit locals let const_0_exit = alloc_value(); // ValueId(9) - exit return value @@ -151,11 +150,8 @@ pub(crate) fn lower_simple_while_minimal( // ================================================================== // loop_step(i) function // ================================================================== - let mut loop_step_func = JoinFunction::new( - loop_step_id, - "loop_step".to_string(), - vec![i_param], - ); + let mut loop_step_func = + JoinFunction::new(loop_step_id, "loop_step".to_string(), vec![i_param]); // exit_cond = !(i < 3) // Step 1: const 3 @@ -196,9 +192,7 @@ pub(crate) fn lower_simple_while_minimal( // Phase 188-Impl-1-E: Use Print instruction loop_step_func .body - .push(JoinInst::Compute(MirLikeInst::Print { - value: i_param, - })); + .push(JoinInst::Compute(MirLikeInst::Print { value: i_param })); // i_next = i + 1 // Step 1: const 1 @@ -223,7 +217,7 @@ pub(crate) fn lower_simple_while_minimal( loop_step_func.body.push(JoinInst::Call { func: loop_step_id, args: vec![i_next], - k_next: None, // CRITICAL: None for tail call + k_next: None, // CRITICAL: None for tail call dst: None, }); diff --git a/src/mir/join_ir/lowering/update_env.rs b/src/mir/join_ir/lowering/update_env.rs index 8feda8d0..581c8ccd 100644 --- a/src/mir/join_ir/lowering/update_env.rs +++ b/src/mir/join_ir/lowering/update_env.rs @@ -167,7 +167,7 @@ impl<'a> UpdateEnv<'a> { // Phase 247-EX: Naming convention - "digit_pos" → "digit_value" (not "digit_pos_value") // Extract base name: "digit_pos" → "digit", "pos" → "pos" let base_name = if promoted_name.ends_with("_pos") { - &promoted_name[..promoted_name.len() - 4] // Remove "_pos" suffix + &promoted_name[..promoted_name.len() - 4] // Remove "_pos" suffix } else { promoted_name.as_str() }; @@ -359,8 +359,8 @@ mod tests { let mut cond_env = ConditionEnv::new(); // Register both carriers in ConditionEnv - cond_env.insert("is_digit_pos".to_string(), ValueId(100)); // Boolean carrier - cond_env.insert("digit_value".to_string(), ValueId(200)); // Integer carrier (digit_pos → digit) + cond_env.insert("is_digit_pos".to_string(), ValueId(100)); // Boolean carrier + cond_env.insert("digit_value".to_string(), ValueId(200)); // Integer carrier (digit_pos → digit) let body_env = LoopBodyLocalEnv::new(); let promoted: Vec = vec!["digit_pos".to_string()]; diff --git a/src/mir/join_ir/mod.rs b/src/mir/join_ir/mod.rs index 7ef32aca..3e3f02cf 100644 --- a/src/mir/join_ir/mod.rs +++ b/src/mir/join_ir/mod.rs @@ -35,6 +35,9 @@ pub mod verify; // Phase 30.x: JSON serialization (jsonir v0) pub mod json; +// Phase 26-H.B: Normalized JoinIR (テスト専用ミニ) +pub mod normalized; + // Phase 34-1: Frontend (AST→JoinIR) — skeleton only pub mod frontend; @@ -44,6 +47,12 @@ pub use lowering::{ }; // Re-export verification functions +pub use normalized::{ + normalize_pattern1_minimal, normalize_pattern2_minimal, normalized_pattern1_to_structured, + normalized_pattern2_to_structured, NormalizedModule, +}; +#[cfg(feature = "normalized_dev")] +pub use normalized::fixtures; pub use verify::verify_progress_for_skip_ws; // Phase 200-3: Contract verification functions are in merge/mod.rs (private module access) @@ -194,6 +203,15 @@ impl LoopExitShape { } } +/// JoinIR フェーズメタデータ。 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum JoinIrPhase { + /// Lowering 直後の構造化 JoinIR(Pattern1–5 / CarrierInfo / Boundary/ExitLine) + Structured, + /// 将来導入予定の正規化済み JoinIR(関数+継続+Env、TailCall-only) + Normalized, +} + /// JoinIR 関数 #[derive(Debug, Clone)] pub struct JoinFunction { @@ -471,9 +489,7 @@ pub enum MirLikeInst { /// Phase 188: Print 文(コンソール出力) /// print(value) の構造を JoinIR で表現 /// MIR 変換時に Print 命令に変換 - Print { - value: VarId, - }, + Print { value: VarId }, /// Phase 188-Impl-3: 条件付き値選択(三項演算子) /// cond が true なら then_val を、false なら else_val を dst に代入 @@ -535,6 +551,9 @@ pub struct JoinModule { /// エントリーポイント関数ID pub entry: Option, + + /// JoinIR のフェーズ(構造化 / 正規化) + pub phase: JoinIrPhase, } impl JoinModule { @@ -542,12 +561,25 @@ impl JoinModule { Self { functions: BTreeMap::new(), entry: None, + phase: JoinIrPhase::Structured, } } pub fn add_function(&mut self, func: JoinFunction) { self.functions.insert(func.id, func); } + + pub fn is_structured(&self) -> bool { + self.phase == JoinIrPhase::Structured + } + + pub fn is_normalized(&self) -> bool { + self.phase == JoinIrPhase::Normalized + } + + pub fn mark_normalized(&mut self) { + self.phase = JoinIrPhase::Normalized; + } } impl Default for JoinModule { @@ -584,6 +616,16 @@ mod tests { assert_eq!(module.functions.len(), 1); assert!(module.functions.contains_key(&JoinFuncId::new(0))); + assert_eq!(module.phase, JoinIrPhase::Structured); + assert!(module.is_structured()); + } + + #[test] + fn test_mark_normalized() { + let mut module = JoinModule::new(); + assert!(module.is_structured()); + module.mark_normalized(); + assert!(module.is_normalized()); } #[test] diff --git a/src/mir/join_ir/normalized.rs b/src/mir/join_ir/normalized.rs new file mode 100644 index 00000000..c4707c33 --- /dev/null +++ b/src/mir/join_ir/normalized.rs @@ -0,0 +1,840 @@ +//! Minimal Normalized JoinIR model (Phase 26-H.B). +//! +//! テスト専用の極小サブセット。Pattern1 の while だけを Structured → Normalized に +//! 変換して遊ぶための足場だよ。本線の Structured→MIR 経路には影響しない。 + +use std::collections::BTreeMap; + +use crate::mir::join_ir::{ + BinOpKind, CompareOp, ConstValue, JoinContId, JoinFuncId, JoinFunction, JoinInst, JoinIrPhase, + JoinModule, MirLikeInst, UnaryOp, +}; +use crate::mir::ValueId; +use std::collections::HashSet; +#[cfg(feature = "normalized_dev")] +use std::panic::{catch_unwind, AssertUnwindSafe}; + +#[cfg(feature = "normalized_dev")] +pub mod fixtures; +#[cfg(feature = "normalized_dev")] +pub(crate) mod shape_guard; +#[cfg(feature = "normalized_dev")] +use crate::mir::join_ir::normalized::shape_guard::NormalizedDevShape; + +/// 環境レイアウト(最小)。 +#[derive(Debug, Clone)] +pub struct EnvLayout { + pub id: u32, + pub fields: Vec, +} + +#[derive(Debug, Clone)] +pub struct EnvField { + pub name: String, + pub ty: Option, + pub value_id: Option, +} + +/// 正規化済み関数 ID +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct JpFuncId(pub u32); + +/// 正規化済み関数(Kont 兼用、is_kont で区別)。 +#[derive(Debug, Clone)] +pub struct JpFunction { + pub id: JpFuncId, + pub name: String, + pub env_layout: Option, + pub body: Vec, + pub is_kont: bool, +} + +/// 正規化済み命令(最小セット)。 +#[derive(Debug, Clone)] +pub enum JpInst { + Let { + dst: ValueId, + op: JpOp, + args: Vec, + }, + EnvLoad { + dst: ValueId, + env: ValueId, + field: usize, + }, + EnvStore { + env: ValueId, + field: usize, + src: ValueId, + }, + TailCallFn { + target: JpFuncId, + env: Vec, + }, + TailCallKont { + target: JpFuncId, + env: Vec, + }, + If { + cond: ValueId, + then_target: JpFuncId, + else_target: JpFuncId, + env: Vec, + }, +} + +/// 演算(Let 用の最小サブセット)。 +#[derive(Debug, Clone)] +pub enum JpOp { + Const(ConstValue), + BinOp(BinOpKind), + Unary(UnaryOp), + Compare(CompareOp), +} + +/// Normalized JoinIR モジュール(テスト専用)。 +#[derive(Debug, Clone)] +pub struct NormalizedModule { + pub functions: BTreeMap, + pub entry: Option, + pub env_layouts: Vec, + pub phase: JoinIrPhase, + /// Structured に戻すためのスナップショット(テスト専用)。 + pub structured_backup: Option, +} + +impl NormalizedModule { + pub fn to_structured(&self) -> Option { + self.structured_backup.clone() + } +} + +#[cfg(feature = "normalized_dev")] +fn verify_normalized_pattern1(module: &NormalizedModule) -> Result<(), String> { + if module.phase != JoinIrPhase::Normalized { + return Err("Normalized verifier: phase must be Normalized".to_string()); + } + + // Env field bounds check + if let Some(env) = module.env_layouts.get(0) { + let field_count = env.fields.len(); + for func in module.functions.values() { + for inst in &func.body { + match inst { + JpInst::EnvLoad { field, .. } | JpInst::EnvStore { field, .. } => { + if *field >= field_count { + return Err(format!( + "Env field out of range: {} (fields={})", + field, field_count + )); + } + } + _ => {} + } + } + } + } + + for func in module.functions.values() { + for inst in &func.body { + match inst { + JpInst::Let { .. } + | JpInst::EnvLoad { .. } + | JpInst::EnvStore { .. } + | JpInst::TailCallFn { .. } + | JpInst::TailCallKont { .. } + | JpInst::If { .. } => {} + } + } + + // Tail: allow TailCall or If only + if let Some(last) = func.body.last() { + match last { + JpInst::TailCallFn { .. } | JpInst::TailCallKont { .. } | JpInst::If { .. } => {} + _ => { + return Err(format!( + "Function '{}' does not end with tail call/if", + func.name + )) + } + } + } + } + + Ok(()) +} + +/// Pattern1 専用: Normalized → Structured への簡易逆変換。 +pub fn normalized_pattern1_to_structured(norm: &NormalizedModule) -> JoinModule { + assert_eq!( + norm.phase, + JoinIrPhase::Normalized, + "normalized_pattern1_to_structured expects Normalized phase" + ); + + let env_layout = norm + .env_layouts + .get(0) + .expect("normalized_pattern1_to_structured: missing env layout"); + + let mut module = JoinModule::new(); + + for (jp_id, jp_fn) in &norm.functions { + let params: Vec = env_layout + .fields + .iter() + .enumerate() + .map(|(idx, f)| f.value_id.unwrap_or(ValueId(idx as u32))) + .collect(); + + let mut func = JoinFunction::new(JoinFuncId(jp_id.0), jp_fn.name.clone(), params); + + for inst in &jp_fn.body { + match inst { + JpInst::Let { dst, op, args } => match op { + JpOp::Const(v) => func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: *dst, + value: v.clone(), + })), + JpOp::BinOp(op) => func.body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: *dst, + op: *op, + lhs: args.get(0).copied().unwrap_or(ValueId(0)), + rhs: args.get(1).copied().unwrap_or(ValueId(0)), + })), + JpOp::Unary(op) => func.body.push(JoinInst::Compute(MirLikeInst::UnaryOp { + dst: *dst, + op: *op, + operand: args.get(0).copied().unwrap_or(ValueId(0)), + })), + JpOp::Compare(op) => func.body.push(JoinInst::Compute(MirLikeInst::Compare { + dst: *dst, + op: *op, + lhs: args.get(0).copied().unwrap_or(ValueId(0)), + rhs: args.get(1).copied().unwrap_or(ValueId(0)), + })), + }, + JpInst::TailCallFn { target, env } => func.body.push(JoinInst::Call { + func: JoinFuncId(target.0), + args: env.clone(), + k_next: None, + dst: None, + }), + JpInst::TailCallKont { target, env } => func.body.push(JoinInst::Jump { + cont: JoinContId(target.0), + args: env.clone(), + cond: None, + }), + JpInst::If { + cond, + then_target, + else_target, + env, + } => { + // Jump to then_target on cond, else jump to else_target (Pattern1 minimal) + func.body.push(JoinInst::Jump { + cont: JoinContId(then_target.0), + args: env.clone(), + cond: Some(*cond), + }); + func.body.push(JoinInst::Jump { + cont: JoinContId(else_target.0), + args: env.clone(), + cond: None, + }); + } + JpInst::EnvLoad { .. } | JpInst::EnvStore { .. } => { + // Not used in Pattern1 minimal; ignore for now + } + } + } + + module.add_function(func); + } + + module.entry = norm.entry.map(|e| JoinFuncId(e.0)); + module.phase = JoinIrPhase::Structured; + module +} + +/// Pattern2 専用のミニ変換(最小サブセット: ループ変数1つ + break、acc などの LoopState を 1 個まで+ホスト 1 個まで)。 +/// +/// 制約: +/// - structured.phase は Structured であること +/// - main/loop_step/k_exit の 3 関数構成(joinir_min_loop 相当) +pub fn normalize_pattern2_minimal(structured: &JoinModule) -> NormalizedModule { + assert!( + structured.is_structured(), + "normalize_pattern2_minimal: expected Structured JoinIR" + ); + + // Minimal guardrail: Pattern2 mini should have main/loop_step/k_exit only, with 1 loop param. + let func_count = structured.functions.len(); + let loop_func = structured + .functions + .values() + .find(|f| f.name == "loop_step") + .or_else(|| structured.functions.get(&JoinFuncId::new(1))) + .expect("normalize_pattern2_minimal: loop_step not found"); + + assert!( + func_count == 3, + "normalize_pattern2_minimal: expected 3 functions (entry/loop_step/k_exit) but got {}", + func_count + ); + assert!( + (1..=3).contains(&loop_func.params.len()), + "normalize_pattern2_minimal: expected 1..=3 params (loop var + optional acc + optional host)" + ); + + let jump_conds = loop_func + .body + .iter() + .filter(|inst| matches!(inst, JoinInst::Jump { cond: Some(_), .. })) + .count(); + let tail_calls = loop_func + .body + .iter() + .filter(|inst| matches!(inst, JoinInst::Call { k_next: None, .. })) + .count(); + assert!( + jump_conds >= 1 && tail_calls >= 1, + "normalize_pattern2_minimal: expected at least one conditional jump and one tail call" + ); + + let env_layout = EnvLayout { + id: 0, + fields: loop_func + .params + .iter() + .enumerate() + .map(|(idx, vid)| EnvField { + name: format!("field{}", idx), + ty: None, + value_id: Some(*vid), + }) + .collect(), + }; + + let mut functions = BTreeMap::new(); + + for (fid, func) in &structured.functions { + let env_layout_id = if func.params.is_empty() { + None + } else { + Some(env_layout.id) + }; + + let mut body = Vec::new(); + for inst in &func.body { + match inst { + JoinInst::Compute(MirLikeInst::Const { dst, value }) => body.push(JpInst::Let { + dst: *dst, + op: JpOp::Const(value.clone()), + args: vec![], + }), + JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => { + body.push(JpInst::Let { + dst: *dst, + op: JpOp::BinOp(*op), + args: vec![*lhs, *rhs], + }) + } + JoinInst::Compute(MirLikeInst::UnaryOp { dst, op, operand }) => { + body.push(JpInst::Let { + dst: *dst, + op: JpOp::Unary(*op), + args: vec![*operand], + }) + } + JoinInst::Compute(MirLikeInst::Compare { dst, op, lhs, rhs }) => { + body.push(JpInst::Let { + dst: *dst, + op: JpOp::Compare(*op), + args: vec![*lhs, *rhs], + }) + } + JoinInst::Jump { cont, args, cond } => { + if let Some(cond_val) = cond { + body.push(JpInst::If { + cond: *cond_val, + then_target: JpFuncId(cont.0), + else_target: JpFuncId(loop_func.id.0), + env: args.clone(), + }); + } else { + body.push(JpInst::TailCallKont { + target: JpFuncId(cont.0), + env: args.clone(), + }); + } + } + JoinInst::Call { func, args, k_next, .. } => { + if k_next.is_none() { + body.push(JpInst::TailCallFn { + target: JpFuncId(func.0), + env: args.clone(), + }); + } + } + _ => { + // Ret / other instructions are ignored in this minimal prototype + } + } + } + + functions.insert( + JpFuncId(fid.0), + JpFunction { + id: JpFuncId(fid.0), + name: func.name.clone(), + env_layout: env_layout_id, + body, + is_kont: func.name.starts_with("k_"), + }, + ); + } + + let norm = NormalizedModule { + functions, + entry: structured.entry.map(|e| JpFuncId(e.0)), + env_layouts: vec![env_layout], + phase: JoinIrPhase::Normalized, + structured_backup: Some(structured.clone()), + }; + + #[cfg(feature = "normalized_dev")] + { + verify_normalized_pattern2(&norm).expect("normalized Pattern2 verifier"); + } + + norm +} + +/// Pattern2 専用: Normalized → Structured への簡易逆変換。 +pub fn normalized_pattern2_to_structured(norm: &NormalizedModule) -> JoinModule { + if let Some(backup) = norm.to_structured() { + return backup; + } + + let env_layout = norm.env_layouts.get(0); + + let mut module = JoinModule::new(); + + for (jp_id, jp_fn) in &norm.functions { + let params: Vec = jp_fn + .env_layout + .and_then(|id| env_layout.filter(|layout| layout.id == id)) + .map(|layout| { + layout + .fields + .iter() + .enumerate() + .map(|(idx, f)| f.value_id.unwrap_or(ValueId(idx as u32))) + .collect() + }) + .unwrap_or_default(); + + let mut func = JoinFunction::new(JoinFuncId(jp_id.0), jp_fn.name.clone(), params); + + for inst in &jp_fn.body { + match inst { + JpInst::Let { dst, op, args } => match op { + JpOp::Const(v) => func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: *dst, + value: v.clone(), + })), + JpOp::BinOp(op) => func.body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: *dst, + op: *op, + lhs: args.get(0).copied().unwrap_or(ValueId(0)), + rhs: args.get(1).copied().unwrap_or(ValueId(0)), + })), + JpOp::Unary(op) => func.body.push(JoinInst::Compute(MirLikeInst::UnaryOp { + dst: *dst, + op: *op, + operand: args.get(0).copied().unwrap_or(ValueId(0)), + })), + JpOp::Compare(op) => func.body.push(JoinInst::Compute(MirLikeInst::Compare { + dst: *dst, + op: *op, + lhs: args.get(0).copied().unwrap_or(ValueId(0)), + rhs: args.get(1).copied().unwrap_or(ValueId(0)), + })), + }, + JpInst::TailCallFn { target, env } => func.body.push(JoinInst::Call { + func: JoinFuncId(target.0), + args: env.clone(), + k_next: None, + dst: None, + }), + JpInst::TailCallKont { target, env } => func.body.push(JoinInst::Jump { + cont: JoinContId(target.0), + args: env.clone(), + cond: None, + }), + JpInst::If { + cond, + then_target, + else_target, + env, + } => { + func.body.push(JoinInst::Jump { + cont: JoinContId(then_target.0), + args: env.clone(), + cond: Some(*cond), + }); + func.body.push(JoinInst::Jump { + cont: JoinContId(else_target.0), + args: env.clone(), + cond: None, + }); + } + JpInst::EnvLoad { .. } | JpInst::EnvStore { .. } => { + // Not used in minimal pattern2 bridge + } + } + } + + module.add_function(func); + } + + module.entry = norm.entry.map(|e| JoinFuncId(e.0)); + module.phase = JoinIrPhase::Structured; + module +} + +#[cfg(feature = "normalized_dev")] +fn verify_normalized_pattern2(module: &NormalizedModule) -> Result<(), String> { + if module.phase != JoinIrPhase::Normalized { + return Err("Normalized verifier (Pattern2): phase must be Normalized".to_string()); + } + + let field_count = module.env_layouts.get(0).map(|e| e.fields.len()); + + if let Some(field_count) = field_count { + if !(1..=3).contains(&field_count) { + return Err(format!( + "Normalized Pattern2 expects 1..=3 env fields, got {}", + field_count + )); + } + } + + for func in module.functions.values() { + for inst in &func.body { + match inst { + JpInst::Let { .. } + | JpInst::EnvLoad { .. } + | JpInst::EnvStore { .. } + | JpInst::TailCallFn { .. } + | JpInst::TailCallKont { .. } + | JpInst::If { .. } => {} + } + + match inst { + JpInst::TailCallFn { env, .. } + | JpInst::TailCallKont { env, .. } + | JpInst::If { env, .. } => { + if env.is_empty() { + return Err("Normalized Pattern2 env must not be empty".to_string()); + } + if let Some(expected) = field_count { + if env.len() > expected { + return Err(format!( + "Normalized Pattern2 env size exceeds layout: env={}, layout={}", + env.len(), + expected + )); + } + } + } + _ => {} + } + } + + if let Some(last) = func.body.last() { + match last { + JpInst::TailCallFn { .. } + | JpInst::TailCallKont { .. } + | JpInst::If { .. } => {} + _ => { + return Err(format!( + "Function '{}' does not end with tail call/if", + func.name + )); + } + } + } + } + + Ok(()) +} + +/// Pattern1 専用のミニ変換。 +/// +/// 制約: +/// - structured.phase は Structured であること +/// - 対象は Pattern1 のシンプル while(break/continue なし) +pub fn normalize_pattern1_minimal(structured: &JoinModule) -> NormalizedModule { + assert!( + structured.is_structured(), + "normalize_pattern1_minimal: expected Structured JoinIR" + ); + + // entry/loop_step/k_exit を前提に、loop_step を拾う + let loop_func = structured + .functions + .values() + .find(|f| f.name == "loop_step") + .or_else(|| structured.functions.get(&JoinFuncId::new(1))) + .expect("normalize_pattern1_minimal: loop_step not found"); + + // EnvLayout をざっくり作る(フィールド名は field0, field1,... で代用) + let env_layout = EnvLayout { + id: 0, + fields: loop_func + .params + .iter() + .enumerate() + .map(|(idx, vid)| EnvField { + name: format!("field{}", idx), + ty: None, + value_id: Some(*vid), + }) + .collect(), + }; + + // loop_step の Compute を Let に写経(Pattern1 では Compute/Call/Ret のみ想定) + let mut extra_konts: HashSet = HashSet::new(); + let mut jp_body = Vec::new(); + for inst in &loop_func.body { + match inst { + JoinInst::Compute(MirLikeInst::Const { dst, value }) => jp_body.push(JpInst::Let { + dst: *dst, + op: JpOp::Const(value.clone()), + args: vec![], + }), + JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => { + jp_body.push(JpInst::Let { + dst: *dst, + op: JpOp::BinOp(*op), + args: vec![*lhs, *rhs], + }) + } + JoinInst::Compute(MirLikeInst::UnaryOp { dst, op, operand }) => { + jp_body.push(JpInst::Let { + dst: *dst, + op: JpOp::Unary(*op), + args: vec![*operand], + }) + } + JoinInst::Compute(MirLikeInst::Compare { dst, op, lhs, rhs }) => { + jp_body.push(JpInst::Let { + dst: *dst, + op: JpOp::Compare(*op), + args: vec![*lhs, *rhs], + }) + } + // Tail recursion / exit は TailCall と If でざっくり表現 + JoinInst::Jump { cont, args, cond } => { + if let Some(cond_val) = cond { + extra_konts.insert(JpFuncId(cont.0)); + jp_body.push(JpInst::If { + cond: *cond_val, + then_target: JpFuncId(cont.0), + else_target: JpFuncId(loop_func.id.0), + env: args.clone(), + }); + } else { + extra_konts.insert(JpFuncId(cont.0)); + jp_body.push(JpInst::TailCallKont { + target: JpFuncId(cont.0), + env: args.clone(), + }); + } + } + JoinInst::Call { func, args, .. } => jp_body.push(JpInst::TailCallFn { + target: JpFuncId(func.0), + env: args.clone(), + }), + JoinInst::Ret { value } => { + if let Some(v) = value { + let kont_id = JpFuncId(loop_func.id.0 + 1); + extra_konts.insert(kont_id); + jp_body.push(JpInst::TailCallKont { + target: kont_id, + env: vec![*v], + }); + } + } + _ => { + // Pattern1 の最小変換なので他は無視(将来拡張) + } + } + } + + let loop_fn = JpFunction { + id: JpFuncId(loop_func.id.0), + name: loop_func.name.clone(), + env_layout: Some(env_layout.id), + body: jp_body, + is_kont: false, + }; + + let mut functions = BTreeMap::new(); + functions.insert(loop_fn.id, loop_fn); + + for kont_id in extra_konts { + functions.entry(kont_id).or_insert_with(|| JpFunction { + id: kont_id, + name: format!("kont_{}", kont_id.0), + env_layout: Some(env_layout.id), + body: Vec::new(), + is_kont: true, + }); + } + + let norm = NormalizedModule { + functions, + entry: Some(JpFuncId(loop_func.id.0)), + env_layouts: vec![env_layout], + phase: JoinIrPhase::Normalized, + structured_backup: Some(structured.clone()), + }; + + #[cfg(feature = "normalized_dev")] + { + verify_normalized_pattern1(&norm).expect("normalized verifier"); + } + + norm +} + +/// Dev helper: Structured → Normalized → Structured roundtrip (Pattern1/2 minis only). +#[cfg(feature = "normalized_dev")] +pub(crate) fn normalized_dev_roundtrip_structured( + module: &JoinModule, +) -> Result { + if !module.is_structured() { + return Err("[joinir/normalized-dev] expected Structured JoinModule".to_string()); + } + + let shapes = shape_guard::supported_shapes(module); + if shapes.is_empty() { + return Err("[joinir/normalized-dev] module shape is not supported by normalized_dev".into()); + } + + let verbose = crate::config::env::joinir_dev_enabled(); + + for shape in shapes { + if verbose { + eprintln!("[joinir/normalized-dev] attempting {:?} normalization", shape); + } + + let attempt = match shape { + NormalizedDevShape::Pattern1Mini => catch_unwind(AssertUnwindSafe(|| { + let norm = normalize_pattern1_minimal(module); + normalized_pattern1_to_structured(&norm) + })), + NormalizedDevShape::Pattern2Mini | NormalizedDevShape::JsonparserSkipWsMini => { + catch_unwind(AssertUnwindSafe(|| { + let norm = normalize_pattern2_minimal(module); + normalized_pattern2_to_structured(&norm) + })) + } + }; + + match attempt { + Ok(structured) => { + if verbose { + eprintln!( + "[joinir/normalized-dev] {:?} normalization succeeded (functions={})", + shape, + structured.functions.len() + ); + } + return Ok(structured); + } + Err(_) => { + if verbose { + eprintln!( + "[joinir/normalized-dev] {:?} normalization failed (unsupported)", + shape + ); + } + } + } + } + + Err("[joinir/normalized-dev] all normalization attempts failed".into()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn build_structured_pattern1() -> JoinModule { + let mut module = JoinModule::new(); + let mut loop_fn = JoinFunction::new( + JoinFuncId::new(1), + "loop_step".to_string(), + vec![ValueId(10)], + ); + + loop_fn.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: ValueId(11), + value: ConstValue::Integer(0), + })); + loop_fn.body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: ValueId(12), + op: BinOpKind::Add, + lhs: ValueId(10), + rhs: ValueId(11), + })); + loop_fn.body.push(JoinInst::Jump { + cont: JoinContId(2), + args: vec![ValueId(12)], + cond: Some(ValueId(12)), // dummy + }); + + let mut k_exit = + JoinFunction::new(JoinFuncId::new(2), "k_exit".to_string(), vec![ValueId(12)]); + k_exit.body.push(JoinInst::Ret { + value: Some(ValueId(12)), + }); + + module.entry = Some(loop_fn.id); + module.add_function(loop_fn); + module.add_function(k_exit); + module + } + + #[test] + fn normalized_pattern1_minimal_smoke() { + let structured = build_structured_pattern1(); + let normalized = normalize_pattern1_minimal(&structured); + assert_eq!(normalized.phase, JoinIrPhase::Normalized); + assert!(!normalized.env_layouts.is_empty()); + assert!(!normalized.functions.is_empty()); + + #[cfg(feature = "normalized_dev")] + { + verify_normalized_pattern1(&normalized).expect("verifier should pass"); + } + + let restored = normalized.to_structured().expect("backup"); + assert!(restored.is_structured()); + assert_eq!(restored.functions.len(), structured.functions.len()); + } + + #[test] + fn normalized_pattern1_roundtrip_structured_equivalent() { + let structured = build_structured_pattern1(); + let normalized = normalize_pattern1_minimal(&structured); + let reconstructed = normalized_pattern1_to_structured(&normalized); + + assert!(reconstructed.is_structured()); + assert_eq!(reconstructed.functions.len(), structured.functions.len()); + } +} diff --git a/src/mir/join_ir/normalized/fixtures.rs b/src/mir/join_ir/normalized/fixtures.rs new file mode 100644 index 00000000..e4ad722a --- /dev/null +++ b/src/mir/join_ir/normalized/fixtures.rs @@ -0,0 +1,114 @@ +#![cfg(feature = "normalized_dev")] + +use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span}; +use crate::mir::join_ir::frontend::AstToJoinIrLowerer; +use crate::mir::join_ir::lowering::condition_env::ConditionEnv; +use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace; +use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; +use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr; +use crate::mir::join_ir::lowering::loop_with_break_minimal::lower_loop_with_break_minimal; +use crate::mir::join_ir::JoinModule; +use crate::mir::{BasicBlockId, ValueId}; +use std::collections::{BTreeMap, BTreeSet}; + +/// Structured Pattern2 (joinir_min_loop 相当) をテスト用に生成するヘルパー。 +pub fn build_pattern2_minimal_structured() -> JoinModule { + let loop_cond = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(3), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + + let break_cond = ASTNode::BinaryOp { + operator: BinaryOperator::GreaterEqual, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(2), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + + let mut scope = LoopScopeShape { + header: BasicBlockId(0), + body: BasicBlockId(1), + latch: BasicBlockId(2), + exit: BasicBlockId(3), + pinned: BTreeSet::new(), + carriers: BTreeSet::new(), + body_locals: BTreeSet::new(), + exit_live: BTreeSet::new(), + progress_carrier: None, + variable_definitions: BTreeMap::new(), + }; + scope.pinned.insert("i".to_string()); + + let mut condition_env = ConditionEnv::new(); + condition_env.insert("i".to_string(), ValueId(100)); + + let carrier_info = crate::mir::join_ir::lowering::carrier_info::CarrierInfo { + loop_var_name: "i".to_string(), + loop_var_id: ValueId(1), + carriers: vec![], + trim_helper: None, + promoted_loopbodylocals: vec![], + }; + + let carrier_updates: BTreeMap = BTreeMap::new(); + let mut join_value_space = JoinValueSpace::new(); + + let (module, _) = lower_loop_with_break_minimal( + scope, + &loop_cond, + &break_cond, + &condition_env, + &carrier_info, + &carrier_updates, + &[], + None, + &mut join_value_space, + ) + .expect("pattern2 minimal lowering should succeed"); + + module +} + +/// Pattern2 ブレークループ(fixture ベース)を Structured で組み立てるヘルパー。 +/// +/// Fixture: docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/loop_frontend_break.program.json +pub fn build_pattern2_break_fixture_structured() -> JoinModule { + const FIXTURE: &str = include_str!( + "../../../../docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/loop_frontend_break.program.json" + ); + + let program_json: serde_json::Value = + serde_json::from_str(FIXTURE).expect("fixture JSON should be valid"); + + let mut lowerer = AstToJoinIrLowerer::new(); + lowerer.lower_program_json(&program_json) +} + +/// JsonParser 由来のミニ P2 ループ(空白スキップ相当)を Structured で組み立てるヘルパー。 +/// +/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_skip_ws_mini.program.json +pub fn build_jsonparser_skip_ws_structured_for_normalized_dev() -> JoinModule { + const FIXTURE: &str = include_str!( + "../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_skip_ws_mini.program.json" + ); + + let program_json: serde_json::Value = + serde_json::from_str(FIXTURE).expect("jsonparser skip_ws fixture should be valid JSON"); + + let mut lowerer = AstToJoinIrLowerer::new(); + lowerer.lower_program_json(&program_json) +} diff --git a/src/mir/join_ir/normalized/shape_guard.rs b/src/mir/join_ir/normalized/shape_guard.rs new file mode 100644 index 00000000..de9e9a39 --- /dev/null +++ b/src/mir/join_ir/normalized/shape_guard.rs @@ -0,0 +1,68 @@ +#![cfg(feature = "normalized_dev")] + +use crate::mir::join_ir::{JoinFuncId, JoinFunction, JoinInst, JoinModule}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum NormalizedDevShape { + Pattern1Mini, + Pattern2Mini, + JsonparserSkipWsMini, +} + +pub(crate) fn supported_shapes(module: &JoinModule) -> Vec { + let mut shapes = Vec::new(); + if is_jsonparser_skip_ws_mini(module) { + shapes.push(NormalizedDevShape::JsonparserSkipWsMini); + } + if is_pattern2_mini(module) { + shapes.push(NormalizedDevShape::Pattern2Mini); + } + if is_pattern1_mini(module) { + shapes.push(NormalizedDevShape::Pattern1Mini); + } + shapes +} + +pub(crate) fn is_pattern1_mini(module: &JoinModule) -> bool { + module.is_structured() && find_loop_step(module).is_some() +} + +pub(crate) fn is_pattern2_mini(module: &JoinModule) -> bool { + if !module.is_structured() || module.functions.len() != 3 { + return false; + } + let loop_func = match find_loop_step(module) { + Some(f) => f, + None => return false, + }; + if !(1..=3).contains(&loop_func.params.len()) { + return false; + } + + let has_cond_jump = loop_func + .body + .iter() + .any(|inst| matches!(inst, JoinInst::Jump { cond: Some(_), .. })); + let has_tail_call = loop_func + .body + .iter() + .any(|inst| matches!(inst, JoinInst::Call { k_next: None, .. })); + + has_cond_jump && has_tail_call +} + +pub(crate) fn is_jsonparser_skip_ws_mini(module: &JoinModule) -> bool { + is_pattern2_mini(module) + && module + .functions + .values() + .any(|f| f.name == "jsonparser_skip_ws_mini") +} + +fn find_loop_step(module: &JoinModule) -> Option<&JoinFunction> { + module + .functions + .values() + .find(|f| f.name == "loop_step") + .or_else(|| module.functions.get(&JoinFuncId::new(1))) +} diff --git a/src/mir/join_ir_runner.rs b/src/mir/join_ir_runner.rs index 90a29264..7e1a24fd 100644 --- a/src/mir/join_ir_runner.rs +++ b/src/mir/join_ir_runner.rs @@ -30,7 +30,11 @@ use std::collections::HashMap; +#[cfg(feature = "normalized_dev")] +use crate::config::env::normalized_dev_enabled; use crate::mir::join_ir::{ConstValue, JoinFuncId, JoinInst, JoinModule, MirLikeInst, VarId}; +#[cfg(feature = "normalized_dev")] +use crate::mir::join_ir::normalized::{normalized_dev_roundtrip_structured, shape_guard}; // Phase 27.8: ops box からの再エクスポート pub use crate::mir::join_ir_ops::{JoinIrOpError, JoinValue}; @@ -44,9 +48,49 @@ pub fn run_joinir_function( entry: JoinFuncId, args: &[JoinValue], ) -> Result { + #[cfg(feature = "normalized_dev")] + if normalized_dev_enabled() { + return run_joinir_function_normalized_dev(vm, module, entry, args); + } + execute_function(vm, module, entry, args.to_vec()) } +#[cfg(feature = "normalized_dev")] +fn run_joinir_function_normalized_dev( + vm: &mut crate::backend::mir_interpreter::MirInterpreter, + module: &JoinModule, + entry: JoinFuncId, + args: &[JoinValue], +) -> Result { + // Keep dev path opt-in and fail-fast: only Structured P1/P2 minis are supported. + let verbose = crate::config::env::joinir_dev_enabled(); + let args_vec = args.to_vec(); + + let shapes = shape_guard::supported_shapes(module); + if shapes.is_empty() { + if verbose { + eprintln!( + "[joinir/runner/normalized-dev] shape unsupported; staying on Structured path" + ); + } + return execute_function(vm, module, entry, args_vec); + } + + let structured_roundtrip = normalized_dev_roundtrip_structured(module) + .map_err(|msg| JoinRuntimeError::new(format!("[joinir/runner/normalized-dev] {}", msg)))?; + + if verbose { + eprintln!( + "[joinir/runner/normalized-dev] normalized roundtrip succeeded (shapes={:?}, functions={})", + shapes, + structured_roundtrip.functions.len() + ); + } + + execute_function(vm, &structured_roundtrip, entry, args_vec) +} + fn execute_function( vm: &mut crate::backend::mir_interpreter::MirInterpreter, module: &JoinModule, diff --git a/src/mir/join_ir_vm_bridge/bridge.rs b/src/mir/join_ir_vm_bridge/bridge.rs new file mode 100644 index 00000000..2b836857 --- /dev/null +++ b/src/mir/join_ir_vm_bridge/bridge.rs @@ -0,0 +1,81 @@ +use super::{convert_join_module_to_mir_with_meta, JoinIrVmBridgeError}; +use crate::mir::join_ir::frontend::JoinFuncMetaMap; +use crate::mir::join_ir::{JoinIrPhase, JoinModule}; +use crate::mir::MirModule; +use std::collections::BTreeMap; + +#[cfg(feature = "normalized_dev")] +use crate::mir::join_ir::normalized::{normalized_dev_roundtrip_structured, NormalizedModule}; +#[cfg(feature = "normalized_dev")] +use crate::mir::join_ir::normalized::shape_guard; + +/// Structured JoinIR → MIR(既存経路)の明示エントリ。 +pub(crate) fn lower_joinir_structured_to_mir_with_meta( + module: &JoinModule, + meta: &JoinFuncMetaMap, +) -> Result { + if !module.is_structured() { + return Err(JoinIrVmBridgeError::new( + "[joinir/bridge] expected Structured JoinIR module", + )); + } + + convert_join_module_to_mir_with_meta(module, meta) +} + +/// Normalized JoinIR → MIR(現状は Structured に戻して既存ブリッジを再利用)。 +#[cfg(feature = "normalized_dev")] +#[allow(dead_code)] +pub(crate) fn lower_joinir_normalized_to_mir_with_meta( + module: &NormalizedModule, + meta: &JoinFuncMetaMap, +) -> Result { + if module.phase != JoinIrPhase::Normalized { + return Err(JoinIrVmBridgeError::new( + "[joinir/bridge] expected Normalized JoinIR module", + )); + } + + let structured = module.to_structured().ok_or_else(|| { + JoinIrVmBridgeError::new( + "[joinir/bridge] normalized module missing Structured snapshot (dev-only)", + ) + })?; + + lower_joinir_structured_to_mir_with_meta(&structured, meta) +} + +/// JoinIR → MIR の単一入口。Normalized dev が有効なら roundtrip してから既存経路へ。 +pub(crate) fn bridge_joinir_to_mir_with_meta( + module: &JoinModule, + meta: &JoinFuncMetaMap, +) -> Result { + if crate::config::env::normalized_dev_enabled() { + #[cfg(feature = "normalized_dev")] + { + let shapes = shape_guard::supported_shapes(module); + if shapes.is_empty() { + debug_log!( + "[joinir/bridge] normalized dev enabled but shape unsupported; falling back to Structured path" + ); + } else { + let structured_roundtrip = normalized_dev_roundtrip_structured(module) + .map_err(JoinIrVmBridgeError::new)?; + debug_log!( + "[joinir/bridge] normalized dev path enabled (shapes={:?}, functions={})", + shapes, + structured_roundtrip.functions.len() + ); + return lower_joinir_structured_to_mir_with_meta(&structured_roundtrip, meta); + } + } + } + + lower_joinir_structured_to_mir_with_meta(module, meta) +} + +/// JoinIR → MIR(メタなし)呼び出しのユーティリティ。 +pub(crate) fn bridge_joinir_to_mir(module: &JoinModule) -> Result { + let empty_meta: JoinFuncMetaMap = BTreeMap::new(); + bridge_joinir_to_mir_with_meta(module, &empty_meta) +} diff --git a/src/mir/join_ir_vm_bridge/convert.rs b/src/mir/join_ir_vm_bridge/convert.rs index 8ba415d4..cd265429 100644 --- a/src/mir/join_ir_vm_bridge/convert.rs +++ b/src/mir/join_ir_vm_bridge/convert.rs @@ -1,8 +1,7 @@ -use crate::mir::join_ir::{ - BinOpKind, CompareOp, ConstValue, JoinModule, MirLikeInst, -}; +use crate::mir::join_ir::{BinOpKind, CompareOp, ConstValue, JoinModule, MirLikeInst}; use crate::mir::{ - BinaryOp, CompareOp as MirCompareOp, ConstValue as MirConstValue, EffectMask, MirInstruction, MirModule, + BinaryOp, CompareOp as MirCompareOp, ConstValue as MirConstValue, EffectMask, MirInstruction, + MirModule, }; use super::JoinIrVmBridgeError; @@ -13,6 +12,7 @@ use super::joinir_function_converter::JoinIrFunctionConverter; /// Phase 190: JoinIR → MIR 変換器(統合エントリーポイント) /// /// Phase 32 L-2.2 Step-3: テストから呼び出し可能に `pub(crate)` 化 +#[cfg_attr(not(test), allow(dead_code))] pub(crate) fn convert_joinir_to_mir( join_module: &JoinModule, ) -> Result { @@ -20,7 +20,6 @@ pub(crate) fn convert_joinir_to_mir( JoinIrFunctionConverter::convert_joinir_to_mir(join_module) } - /// MirLikeInst → MirInstruction 変換 /// Phase 190: 共有ユーティリティとして pub(crate) に変更 pub(crate) fn convert_mir_like_inst( diff --git a/src/mir/join_ir_vm_bridge/joinir_block_converter.rs b/src/mir/join_ir_vm_bridge/joinir_block_converter.rs index 0162d054..8e382328 100644 --- a/src/mir/join_ir_vm_bridge/joinir_block_converter.rs +++ b/src/mir/join_ir_vm_bridge/joinir_block_converter.rs @@ -51,8 +51,17 @@ impl JoinIrBlockConverter { // Phase 189: Special handling for MirLikeInst::Select // Pattern 3 uses JoinInst::Compute(MirLikeInst::Select {...}) // but Select needs control flow expansion (Branch + Phi), not single instruction - if let MirLikeInst::Select { dst, cond, then_val, else_val } = mir_like { - eprintln!("[joinir_block] ✅ Found Select! dst={:?}, calling handle_select", dst); + if let MirLikeInst::Select { + dst, + cond, + then_val, + else_val, + } = mir_like + { + eprintln!( + "[joinir_block] ✅ Found Select! dst={:?}, calling handle_select", + dst + ); self.handle_select(mir_func, dst, cond, then_val, else_val, &None)?; continue; } @@ -77,7 +86,9 @@ impl JoinIrBlockConverter { method, args, } => { - self.handle_conditional_method_call(mir_func, cond, dst, receiver, method, args)?; + self.handle_conditional_method_call( + mir_func, cond, dst, receiver, method, args, + )?; } JoinInst::FieldAccess { dst, object, field } => { self.handle_field_access(dst, object, field)?; @@ -292,7 +303,8 @@ impl JoinIrBlockConverter { // Phase 189: Use stable function name → ValueId mapping // This ensures the same function name always gets the same ValueId, // which is critical for tail call detection in merge_joinir_mir_blocks - let func_name_id = *self.func_name_to_value_id + let func_name_id = *self + .func_name_to_value_id .entry(func_name.clone()) .or_insert_with(|| { let id = ValueId(self.next_func_name_value_id); @@ -701,7 +713,11 @@ impl JoinIrBlockConverter { instructions: Vec, terminator: MirInstruction, ) { - eprintln!("[joinir_block/finalize_block] block_id={:?}, instructions.len()={}", block_id, instructions.len()); + eprintln!( + "[joinir_block/finalize_block] block_id={:?}, instructions.len()={}", + block_id, + instructions.len() + ); if let Some(block) = mir_func.blocks.get_mut(&block_id) { // Phase 189 FIX: Preserve existing PHI instructions at block start // PHI instructions must remain at the beginning of the block @@ -746,7 +762,9 @@ impl JoinIrBlockConverter { let new_insts = std::mem::take(&mut self.current_instructions); let new_count = new_insts.len(); block.instructions.extend(new_insts); - block.instruction_spans.extend(vec![Span::unknown(); new_count]); + block + .instruction_spans + .extend(vec![Span::unknown(); new_count]); } } } diff --git a/src/mir/join_ir_vm_bridge/joinir_function_converter.rs b/src/mir/join_ir_vm_bridge/joinir_function_converter.rs index 033ce753..6408a0c6 100644 --- a/src/mir/join_ir_vm_bridge/joinir_function_converter.rs +++ b/src/mir/join_ir_vm_bridge/joinir_function_converter.rs @@ -6,13 +6,11 @@ //! - 関数署名の管理 use crate::mir::join_ir::{JoinFunction, JoinModule}; -use crate::mir::{ - BasicBlockId, EffectMask, FunctionSignature, MirFunction, MirModule, MirType, -}; +use crate::mir::{BasicBlockId, EffectMask, FunctionSignature, MirFunction, MirModule, MirType}; +use super::join_func_name; use super::joinir_block_converter::JoinIrBlockConverter; use super::JoinIrVmBridgeError; -use super::join_func_name; pub(crate) struct JoinIrFunctionConverter; @@ -20,6 +18,7 @@ impl JoinIrFunctionConverter { /// JoinIR モジュール全体を MIR モジュールに変換 /// /// Phase 32 L-2.2 Step-3: テストから呼び出し可能に `pub(crate)` 化 + #[cfg_attr(not(test), allow(dead_code))] pub(crate) fn convert_joinir_to_mir( join_module: &JoinModule, ) -> Result { @@ -121,7 +120,6 @@ impl JoinIrFunctionConverter { #[cfg(test)] mod tests { - #[test] fn test_function_converter_exists() { diff --git a/src/mir/join_ir_vm_bridge/meta.rs b/src/mir/join_ir_vm_bridge/meta.rs index 562dc4fe..6a1655fc 100644 --- a/src/mir/join_ir_vm_bridge/meta.rs +++ b/src/mir/join_ir_vm_bridge/meta.rs @@ -54,7 +54,10 @@ pub fn convert_join_module_to_mir_with_meta( block_id, block.instructions.len(), phi_count, - block.terminator.as_ref().map(|t| format!("{:?}", t).chars().take(40).collect::()) + block + .terminator + .as_ref() + .map(|t| format!("{:?}", t).chars().take(40).collect::()) ); } diff --git a/src/mir/join_ir_vm_bridge/mod.rs b/src/mir/join_ir_vm_bridge/mod.rs index ce60575d..fb80d38c 100644 --- a/src/mir/join_ir_vm_bridge/mod.rs +++ b/src/mir/join_ir_vm_bridge/mod.rs @@ -38,8 +38,9 @@ mod logging { mod convert; // Phase 190: Modular converters -mod joinir_function_converter; +mod bridge; mod joinir_block_converter; +mod joinir_function_converter; mod meta; mod runner; @@ -47,6 +48,8 @@ mod runner; mod tests; // Phase 190: Use modularized converters +pub(crate) use bridge::{bridge_joinir_to_mir, bridge_joinir_to_mir_with_meta}; +#[allow(unused_imports)] pub(crate) use convert::convert_joinir_to_mir; pub(crate) use convert::convert_mir_like_inst; // helper for sub-modules pub(crate) use joinir_function_converter::JoinIrFunctionConverter; diff --git a/src/mir/join_ir_vm_bridge/runner.rs b/src/mir/join_ir_vm_bridge/runner.rs index 68733e33..d3a372a8 100644 --- a/src/mir/join_ir_vm_bridge/runner.rs +++ b/src/mir/join_ir_vm_bridge/runner.rs @@ -1,4 +1,4 @@ -use super::{convert_joinir_to_mir, join_func_name, JoinIrVmBridgeError}; +use super::{bridge_joinir_to_mir, join_func_name, JoinIrVmBridgeError}; use crate::backend::{MirInterpreter, VMValue}; use crate::mir::join_ir::JoinFuncId; use crate::mir::join_ir::JoinModule; @@ -34,7 +34,7 @@ pub fn run_joinir_via_vm( debug_log!("[joinir_vm_bridge] Converting JoinIR to MIR for VM execution"); // Step 1: JoinIR → MIR 変換 - let mir_module = convert_joinir_to_mir(join_module)?; + let mir_module = bridge_joinir_to_mir(join_module)?; debug_log!( "[joinir_vm_bridge] Converted {} JoinIR functions to MIR", diff --git a/src/mir/join_ir_vm_bridge_dispatch/exec_routes.rs b/src/mir/join_ir_vm_bridge_dispatch/exec_routes.rs index 9d55be6c..dc4bd16e 100644 --- a/src/mir/join_ir_vm_bridge_dispatch/exec_routes.rs +++ b/src/mir/join_ir_vm_bridge_dispatch/exec_routes.rs @@ -23,8 +23,8 @@ pub(crate) fn try_run_skip_ws(module: &MirModule, quiet_pipe: bool) -> bool { let input = std::env::var("NYASH_JOINIR_INPUT").unwrap_or_else(|_| " abc".to_string()); eprintln!("[joinir/vm_bridge] Input: {:?}", input); - let dev_bridge = joinir_dev_enabled() - || std::env::var("NYASH_EMIT_MIR_TRACE").ok().as_deref() == Some("1"); + let dev_bridge = + joinir_dev_enabled() || std::env::var("NYASH_EMIT_MIR_TRACE").ok().as_deref() == Some("1"); let strict = joinir_strict_enabled(); match run_joinir_via_vm(&join_module, JoinFuncId::new(0), &[JoinValue::Str(input)]) { @@ -83,8 +83,8 @@ pub(crate) fn try_run_trim(module: &MirModule, quiet_pipe: bool) -> bool { let input = std::env::var("NYASH_JOINIR_INPUT").unwrap_or_else(|_| " abc ".to_string()); eprintln!("[joinir/vm_bridge] Input: {:?}", input); - let dev_bridge = joinir_dev_enabled() - || std::env::var("NYASH_EMIT_MIR_TRACE").ok().as_deref() == Some("1"); + let dev_bridge = + joinir_dev_enabled() || std::env::var("NYASH_EMIT_MIR_TRACE").ok().as_deref() == Some("1"); let strict = joinir_strict_enabled(); match run_joinir_via_vm(&join_module, JoinFuncId::new(0), &[JoinValue::Str(input)]) { @@ -156,9 +156,10 @@ where G: Fn(&JoinValue) -> String, H: Fn(&JoinValue) -> i32, { - get_global_ring0() - .log - .info(&format!("[joinir/vm_bridge] Attempting JoinIR path for {}", route_name)); + get_global_ring0().log.info(&format!( + "[joinir/vm_bridge] Attempting JoinIR path for {}", + route_name + )); let Some(join_module) = lowerer() else { get_global_ring0().log.info(&format!( @@ -171,15 +172,16 @@ where return false; }; - let dev_bridge = joinir_dev_enabled() - || std::env::var("NYASH_EMIT_MIR_TRACE").ok().as_deref() == Some("1"); + let dev_bridge = + joinir_dev_enabled() || std::env::var("NYASH_EMIT_MIR_TRACE").ok().as_deref() == Some("1"); let strict = joinir_strict_enabled(); match run_joinir_via_vm(&join_module, JoinFuncId::new(0), &[input_val]) { Ok(result) => { - get_global_ring0() - .log - .info(&format!("[joinir/vm_bridge] ✅ JoinIR result: {:?}", result)); + get_global_ring0().log.info(&format!( + "[joinir/vm_bridge] ✅ JoinIR result: {:?}", + result + )); let output = output_formatter(&result); let exit_code = exit_code_extractor(&result); @@ -201,9 +203,10 @@ where } } Err(e) => { - get_global_ring0() - .log - .info(&format!("[joinir/vm_bridge] ❌ JoinIR {} failed: {:?}", route_name, e)); + get_global_ring0().log.info(&format!( + "[joinir/vm_bridge] ❌ JoinIR {} failed: {:?}", + route_name, e + )); get_global_ring0() .log .info("[joinir/vm_bridge] Falling back to normal VM path"); diff --git a/src/mir/loop_pattern_detection/break_condition_analyzer.rs b/src/mir/loop_pattern_detection/break_condition_analyzer.rs index 5543f00b..2e5edeea 100644 --- a/src/mir/loop_pattern_detection/break_condition_analyzer.rs +++ b/src/mir/loop_pattern_detection/break_condition_analyzer.rs @@ -251,9 +251,7 @@ mod tests { span: Span::unknown(), }; - assert!(BreakConditionAnalyzer::has_break_in_else_clause(&[ - if_stmt - ])); + assert!(BreakConditionAnalyzer::has_break_in_else_clause(&[if_stmt])); } #[test] @@ -448,9 +446,7 @@ mod tests { // Should be wrapped in UnaryOp::Not if let ASTNode::UnaryOp { - operator, - operand, - .. + operator, operand, .. } = negated { assert!(matches!(operator, UnaryOperator::Not)); diff --git a/src/mir/loop_pattern_detection/condition_var_analyzer.rs b/src/mir/loop_pattern_detection/condition_var_analyzer.rs index 3a777828..5835fb41 100644 --- a/src/mir/loop_pattern_detection/condition_var_analyzer.rs +++ b/src/mir/loop_pattern_detection/condition_var_analyzer.rs @@ -124,10 +124,7 @@ pub fn extract_all_variables(node: &ASTNode) -> HashSet { /// Future versions may include: /// - Dominance tree analysis /// - More sophisticated scope inference -pub(crate) fn is_outer_scope_variable( - var_name: &str, - scope: Option<&LoopScopeShape>, -) -> bool { +pub(crate) fn is_outer_scope_variable(var_name: &str, scope: Option<&LoopScopeShape>) -> bool { match scope { // No scope information: be conservative but *not* over‑strict. // We treat unknown as body-local only when we have a LoopScopeShape @@ -162,7 +159,10 @@ pub(crate) fn is_outer_scope_variable( // ... // i = i + 1 (latch) // } - if def_blocks.iter().all(|b| *b == scope.header || *b == scope.latch) { + if def_blocks + .iter() + .all(|b| *b == scope.header || *b == scope.latch) + { return true; } @@ -423,7 +423,10 @@ mod tests { let vars = extract_all_variables(&literal_node); - assert!(vars.is_empty(), "Literal-only condition should extract no variables"); + assert!( + vars.is_empty(), + "Literal-only condition should extract no variables" + ); } #[test] @@ -453,14 +456,16 @@ mod tests { }; // Phase 170-ultrathink: header+latch ONLY → OuterLocal (carrier variable) - assert!(is_outer_scope_variable("i", Some(&scope)), - "Carrier variable (header+latch only) should be classified as OuterLocal"); + assert!( + is_outer_scope_variable("i", Some(&scope)), + "Carrier variable (header+latch only) should be classified as OuterLocal" + ); } #[test] fn test_scope_priority_in_add_var() { // Test Phase 170-ultrathink scope priority system - use super::super::loop_condition_scope::{LoopConditionScope, CondVarScope}; + use super::super::loop_condition_scope::{CondVarScope, LoopConditionScope}; let mut scope = LoopConditionScope::new(); @@ -472,19 +477,28 @@ mod tests { // Add same variable as OuterLocal (more restrictive) scope.add_var("x".to_string(), CondVarScope::OuterLocal); assert_eq!(scope.vars.len(), 1, "Should not duplicate variable"); - assert_eq!(scope.vars[0].scope, CondVarScope::OuterLocal, - "Should upgrade to more restrictive OuterLocal"); + assert_eq!( + scope.vars[0].scope, + CondVarScope::OuterLocal, + "Should upgrade to more restrictive OuterLocal" + ); // Try to downgrade to LoopBodyLocal (should be rejected) scope.add_var("x".to_string(), CondVarScope::LoopBodyLocal); assert_eq!(scope.vars.len(), 1); - assert_eq!(scope.vars[0].scope, CondVarScope::OuterLocal, - "Should NOT downgrade from OuterLocal to LoopBodyLocal"); + assert_eq!( + scope.vars[0].scope, + CondVarScope::OuterLocal, + "Should NOT downgrade from OuterLocal to LoopBodyLocal" + ); // Add same variable as LoopParam (most restrictive) scope.add_var("x".to_string(), CondVarScope::LoopParam); assert_eq!(scope.vars.len(), 1); - assert_eq!(scope.vars[0].scope, CondVarScope::LoopParam, - "Should upgrade to most restrictive LoopParam"); + assert_eq!( + scope.vars[0].scope, + CondVarScope::LoopParam, + "Should upgrade to most restrictive LoopParam" + ); } } diff --git a/src/mir/loop_pattern_detection/error_messages.rs b/src/mir/loop_pattern_detection/error_messages.rs index bf9ed75a..1db3f252 100644 --- a/src/mir/loop_pattern_detection/error_messages.rs +++ b/src/mir/loop_pattern_detection/error_messages.rs @@ -43,7 +43,10 @@ pub fn format_unsupported_condition_error( Consider using Pattern 5+ for complex loop conditions.", pattern_name, body_local_names, - pattern_name.chars().filter(|c| c.is_numeric()).collect::() + pattern_name + .chars() + .filter(|c| c.is_numeric()) + .collect::() ) } diff --git a/src/mir/loop_pattern_detection/function_scope_capture.rs b/src/mir/loop_pattern_detection/function_scope_capture.rs index 5d52ee8c..c5177563 100644 --- a/src/mir/loop_pattern_detection/function_scope_capture.rs +++ b/src/mir/loop_pattern_detection/function_scope_capture.rs @@ -30,9 +30,9 @@ //! Phase 200-A creates the infrastructure to capture such variables. //! Phase 200-B will implement the actual detection logic. -use crate::mir::ValueId; -use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; use crate::ast::ASTNode; +use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; +use crate::mir::ValueId; use std::collections::BTreeSet; /// A variable captured from function scope for use in loop conditions/body. @@ -155,7 +155,9 @@ pub(crate) fn analyze_captured_vars( Some(idx) => idx, None => { if debug { - eprintln!("[capture/debug] Loop not found in function body, returning empty CapturedEnv"); + eprintln!( + "[capture/debug] Loop not found in function body, returning empty CapturedEnv" + ); } return CapturedEnv::new(); } @@ -169,7 +171,10 @@ pub(crate) fn analyze_captured_vars( let pre_loop_locals = collect_local_declarations(&fn_body[..loop_index]); if debug { - eprintln!("[capture/debug] Found {} pre-loop local declarations", pre_loop_locals.len()); + eprintln!( + "[capture/debug] Found {} pre-loop local declarations", + pre_loop_locals.len() + ); } let mut env = CapturedEnv::new(); @@ -230,7 +235,10 @@ pub(crate) fn analyze_captured_vars( // Note: We don't have access to variable_map here, so we use a placeholder ValueId // The actual host_id will be resolved in ConditionEnvBuilder if debug { - eprintln!("[capture/accept] '{}': ALL CHECKS PASSED, adding to CapturedEnv", name); + eprintln!( + "[capture/accept] '{}': ALL CHECKS PASSED, adding to CapturedEnv", + name + ); } env.add_var(CapturedVar { @@ -241,7 +249,8 @@ pub(crate) fn analyze_captured_vars( } if debug { - eprintln!("[capture/result] Captured {} variables: {:?}", + eprintln!( + "[capture/result] Captured {} variables: {:?}", env.vars.len(), env.vars.iter().map(|v| &v.name).collect::>() ); @@ -301,7 +310,10 @@ pub(crate) fn analyze_captured_vars_v2( }; if debug { - eprintln!("[capture/debug] Found {} pre-loop local declarations", pre_loop_locals.len()); + eprintln!( + "[capture/debug] Found {} pre-loop local declarations", + pre_loop_locals.len() + ); } let mut env = CapturedEnv::new(); @@ -360,7 +372,10 @@ pub(crate) fn analyze_captured_vars_v2( // All checks passed: add to CapturedEnv if debug { - eprintln!("[capture/accept] '{}': ALL CHECKS PASSED, adding to CapturedEnv", name); + eprintln!( + "[capture/accept] '{}': ALL CHECKS PASSED, adding to CapturedEnv", + name + ); } env.add_var(CapturedVar { @@ -374,8 +389,10 @@ pub(crate) fn analyze_captured_vars_v2( let names_in_loop = collect_names_in_loop_parts(loop_condition, loop_body); // pre-loop local names (already processed above) - let pre_loop_local_names: BTreeSet = - pre_loop_locals.iter().map(|(name, _)| name.clone()).collect(); + let pre_loop_local_names: BTreeSet = pre_loop_locals + .iter() + .map(|(name, _)| name.clone()) + .collect(); // Check each variable used in loop for name in names_in_loop { @@ -402,7 +419,10 @@ pub(crate) fn analyze_captured_vars_v2( // This is a function parameter-like variable - add to CapturedEnv if debug { - eprintln!("[capture/param/accept] '{}': function parameter used in loop", name); + eprintln!( + "[capture/param/accept] '{}': function parameter used in loop", + name + ); } env.add_var(CapturedVar { @@ -413,7 +433,8 @@ pub(crate) fn analyze_captured_vars_v2( } if debug { - eprintln!("[capture/result] Captured {} variables: {:?}", + eprintln!( + "[capture/result] Captured {} variables: {:?}", env.vars.len(), env.vars.iter().map(|v| &v.name).collect::>() ); @@ -428,9 +449,9 @@ pub(crate) fn analyze_captured_vars_v2( #[allow(dead_code)] fn find_stmt_index(fn_body: &[ASTNode], loop_ast: &ASTNode) -> Option { // Compare by pointer address (same AST node instance) - fn_body.iter().position(|stmt| { - std::ptr::eq(stmt as *const ASTNode, loop_ast as *const ASTNode) - }) + fn_body + .iter() + .position(|stmt| std::ptr::eq(stmt as *const ASTNode, loop_ast as *const ASTNode)) } /// Phase 200-C: Find loop index by structure matching (condition + body comparison) @@ -443,7 +464,10 @@ fn find_loop_index_by_structure( target_body: &[ASTNode], ) -> Option { for (idx, stmt) in fn_body.iter().enumerate() { - if let ASTNode::Loop { condition, body, .. } = stmt { + if let ASTNode::Loop { + condition, body, .. + } = stmt + { // Compare condition and body by structure if ast_matches(condition, target_condition) && body_matches(body, target_body) { return Some(idx); @@ -476,7 +500,12 @@ fn collect_local_declarations(stmts: &[ASTNode]) -> Vec<(String, Option bool { } // Grouped assignment expression: (x = expr) - ASTNode::GroupedAssignmentExpr { lhs, rhs, .. } => { - lhs == name || check_node(rhs, name) - } + ASTNode::GroupedAssignmentExpr { lhs, rhs, .. } => lhs == name || check_node(rhs, name), // Recursive cases - ASTNode::If { condition, then_body, else_body, .. } => { + ASTNode::If { + condition, + then_body, + else_body, + .. + } => { check_node(condition, name) || then_body.iter().any(|n| check_node(n, name)) - || else_body.as_ref().map_or(false, |body| body.iter().any(|n| check_node(n, name))) + || else_body + .as_ref() + .map_or(false, |body| body.iter().any(|n| check_node(n, name))) } - ASTNode::Loop { condition, body, .. } => { - check_node(condition, name) || body.iter().any(|n| check_node(n, name)) - } + ASTNode::Loop { + condition, body, .. + } => check_node(condition, name) || body.iter().any(|n| check_node(n, name)), - ASTNode::While { condition, body, .. } => { - check_node(condition, name) || body.iter().any(|n| check_node(n, name)) - } + ASTNode::While { + condition, body, .. + } => check_node(condition, name) || body.iter().any(|n| check_node(n, name)), - ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => { + ASTNode::TryCatch { + try_body, + catch_clauses, + finally_body, + .. + } => { try_body.iter().any(|n| check_node(n, name)) - || catch_clauses.iter().any(|clause| clause.body.iter().any(|n| check_node(n, name))) - || finally_body.as_ref().map_or(false, |body| body.iter().any(|n| check_node(n, name))) + || catch_clauses + .iter() + .any(|clause| clause.body.iter().any(|n| check_node(n, name))) + || finally_body + .as_ref() + .map_or(false, |body| body.iter().any(|n| check_node(n, name))) } ASTNode::UnaryOp { operand, .. } => check_node(operand, name), @@ -559,9 +602,9 @@ fn is_reassigned_in_fn(fn_body: &[ASTNode], name: &str) -> bool { check_node(left, name) || check_node(right, name) } - ASTNode::MethodCall { object, arguments, .. } => { - check_node(object, name) || arguments.iter().any(|arg| check_node(arg, name)) - } + ASTNode::MethodCall { + object, arguments, .. + } => check_node(object, name) || arguments.iter().any(|arg| check_node(arg, name)), ASTNode::FunctionCall { arguments, .. } => { arguments.iter().any(|arg| check_node(arg, name)) @@ -573,9 +616,7 @@ fn is_reassigned_in_fn(fn_body: &[ASTNode], name: &str) -> bool { check_node(target, name) || check_node(index, name) } - ASTNode::Return { value, .. } => { - value.as_ref().map_or(false, |v| check_node(v, name)) - } + ASTNode::Return { value, .. } => value.as_ref().map_or(false, |v| check_node(v, name)), ASTNode::Local { .. } => { // Local declarations are not reassignments @@ -598,14 +639,21 @@ fn is_used_in_loop(loop_ast: &ASTNode, name: &str) -> bool { match node { ASTNode::Variable { name: var_name, .. } => var_name == name, - ASTNode::Loop { condition, body, .. } => { - check_usage(condition, name) || body.iter().any(|n| check_usage(n, name)) - } + ASTNode::Loop { + condition, body, .. + } => check_usage(condition, name) || body.iter().any(|n| check_usage(n, name)), - ASTNode::If { condition, then_body, else_body, .. } => { + ASTNode::If { + condition, + then_body, + else_body, + .. + } => { check_usage(condition, name) || then_body.iter().any(|n| check_usage(n, name)) - || else_body.as_ref().map_or(false, |body| body.iter().any(|n| check_usage(n, name))) + || else_body + .as_ref() + .map_or(false, |body| body.iter().any(|n| check_usage(n, name))) } ASTNode::Assignment { target, value, .. } => { @@ -618,9 +666,9 @@ fn is_used_in_loop(loop_ast: &ASTNode, name: &str) -> bool { check_usage(left, name) || check_usage(right, name) } - ASTNode::MethodCall { object, arguments, .. } => { - check_usage(object, name) || arguments.iter().any(|arg| check_usage(arg, name)) - } + ASTNode::MethodCall { + object, arguments, .. + } => check_usage(object, name) || arguments.iter().any(|arg| check_usage(arg, name)), ASTNode::FunctionCall { arguments, .. } => { arguments.iter().any(|arg| check_usage(arg, name)) @@ -632,15 +680,11 @@ fn is_used_in_loop(loop_ast: &ASTNode, name: &str) -> bool { check_usage(target, name) || check_usage(index, name) } - ASTNode::Return { value, .. } => { - value.as_ref().map_or(false, |v| check_usage(v, name)) - } + ASTNode::Return { value, .. } => value.as_ref().map_or(false, |v| check_usage(v, name)), - ASTNode::Local { initial_values, .. } => { - initial_values.iter().any(|opt| { - opt.as_ref().map_or(false, |init| check_usage(init, name)) - }) - } + ASTNode::Local { initial_values, .. } => initial_values + .iter() + .any(|opt| opt.as_ref().map_or(false, |init| check_usage(init, name))), _ => false, } @@ -657,14 +701,21 @@ fn is_used_in_loop_parts(condition: &ASTNode, body: &[ASTNode], name: &str) -> b match node { ASTNode::Variable { name: var_name, .. } => var_name == name, - ASTNode::Loop { condition, body, .. } => { - check_usage(condition, name) || body.iter().any(|n| check_usage(n, name)) - } + ASTNode::Loop { + condition, body, .. + } => check_usage(condition, name) || body.iter().any(|n| check_usage(n, name)), - ASTNode::If { condition, then_body, else_body, .. } => { + ASTNode::If { + condition, + then_body, + else_body, + .. + } => { check_usage(condition, name) || then_body.iter().any(|n| check_usage(n, name)) - || else_body.as_ref().map_or(false, |body| body.iter().any(|n| check_usage(n, name))) + || else_body + .as_ref() + .map_or(false, |body| body.iter().any(|n| check_usage(n, name))) } ASTNode::Assignment { target, value, .. } => { @@ -677,9 +728,9 @@ fn is_used_in_loop_parts(condition: &ASTNode, body: &[ASTNode], name: &str) -> b check_usage(left, name) || check_usage(right, name) } - ASTNode::MethodCall { object, arguments, .. } => { - check_usage(object, name) || arguments.iter().any(|arg| check_usage(arg, name)) - } + ASTNode::MethodCall { + object, arguments, .. + } => check_usage(object, name) || arguments.iter().any(|arg| check_usage(arg, name)), ASTNode::FunctionCall { arguments, .. } => { arguments.iter().any(|arg| check_usage(arg, name)) @@ -691,15 +742,11 @@ fn is_used_in_loop_parts(condition: &ASTNode, body: &[ASTNode], name: &str) -> b check_usage(target, name) || check_usage(index, name) } - ASTNode::Return { value, .. } => { - value.as_ref().map_or(false, |v| check_usage(v, name)) - } + ASTNode::Return { value, .. } => value.as_ref().map_or(false, |v| check_usage(v, name)), - ASTNode::Local { initial_values, .. } => { - initial_values.iter().any(|opt| { - opt.as_ref().map_or(false, |init| check_usage(init, name)) - }) - } + ASTNode::Local { initial_values, .. } => initial_values + .iter() + .any(|opt| opt.as_ref().map_or(false, |init| check_usage(init, name))), _ => false, } @@ -718,7 +765,12 @@ fn collect_names_in_loop_parts(condition: &ASTNode, body: &[ASTNode]) -> BTreeSe ASTNode::Variable { name, .. } => { acc.insert(name.clone()); } - ASTNode::If { condition, then_body, else_body, .. } => { + ASTNode::If { + condition, + then_body, + else_body, + .. + } => { collect(condition, acc); for stmt in then_body { collect(stmt, acc); @@ -736,14 +788,19 @@ fn collect_names_in_loop_parts(condition: &ASTNode, body: &[ASTNode]) -> BTreeSe ASTNode::UnaryOp { operand, .. } => { collect(operand, acc); } - ASTNode::Return { value: Some(operand), .. } => { + ASTNode::Return { + value: Some(operand), + .. + } => { collect(operand, acc); } ASTNode::BinaryOp { left, right, .. } => { collect(left, acc); collect(right, acc); } - ASTNode::MethodCall { object, arguments, .. } => { + ASTNode::MethodCall { + object, arguments, .. + } => { collect(object, acc); for arg in arguments { collect(arg, acc); @@ -768,7 +825,9 @@ fn collect_names_in_loop_parts(condition: &ASTNode, body: &[ASTNode]) -> BTreeSe collect(target, acc); collect(index, acc); } - ASTNode::Loop { condition, body, .. } => { + ASTNode::Loop { + condition, body, .. + } => { collect(condition, acc); for stmt in body { collect(stmt, acc); @@ -890,8 +949,8 @@ mod tests { let fn_body = vec![digits_decl, loop_node.clone()]; - use std::collections::{BTreeSet, BTreeMap}; use crate::mir::BasicBlockId; + use std::collections::{BTreeMap, BTreeSet}; let scope = crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape { header: BasicBlockId(0), @@ -982,8 +1041,8 @@ mod tests { let fn_body = vec![digits_decl, reassignment, loop_node.clone()]; - use std::collections::{BTreeSet, BTreeMap}; use crate::mir::BasicBlockId; + use std::collections::{BTreeMap, BTreeSet}; let scope = crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape { header: BasicBlockId(0), @@ -1040,8 +1099,8 @@ mod tests { let fn_body = vec![loop_node.clone(), digits_decl]; - use std::collections::{BTreeSet, BTreeMap}; use crate::mir::BasicBlockId; + use std::collections::{BTreeMap, BTreeSet}; let scope = crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape { header: BasicBlockId(0), @@ -1119,8 +1178,8 @@ mod tests { let fn_body = vec![result_decl, loop_node.clone()]; - use std::collections::{BTreeSet, BTreeMap}; use crate::mir::BasicBlockId; + use std::collections::{BTreeMap, BTreeSet}; let scope = crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape { header: BasicBlockId(0), @@ -1173,14 +1232,14 @@ mod tests { }), span: Span::unknown(), }), - body: vec![], // empty body, no usage of digits + body: vec![], // empty body, no usage of digits span: Span::unknown(), }; let fn_body = vec![digits_decl, loop_node.clone()]; - use std::collections::{BTreeSet, BTreeMap}; use crate::mir::BasicBlockId; + use std::collections::{BTreeMap, BTreeSet}; let scope = crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape { header: BasicBlockId(0), @@ -1205,7 +1264,7 @@ mod tests { #[test] fn test_capture_function_param_used_in_condition() { - use crate::ast::{ASTNode, LiteralValue, Span, BinaryOperator}; + use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span}; // Simulate: fn parse_number(s, p, len) { loop(p < len) { ... } } // Expected: 'len' should be captured (used in condition, not reassigned) @@ -1223,30 +1282,28 @@ mod tests { span: Span::unknown(), }); - let body = vec![ - ASTNode::Assignment { - target: Box::new(ASTNode::Variable { + let body = vec![ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "p".to_string(), + span: Span::unknown(), + }), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(ASTNode::Variable { name: "p".to_string(), span: Span::unknown(), }), - value: Box::new(ASTNode::BinaryOp { - operator: BinaryOperator::Add, - left: Box::new(ASTNode::Variable { - name: "p".to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(1), - span: Span::unknown(), - }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(1), span: Span::unknown(), }), span: Span::unknown(), - }, - ]; + }), + span: Span::unknown(), + }]; - use std::collections::{BTreeSet, BTreeMap}; use crate::mir::BasicBlockId; + use std::collections::{BTreeMap, BTreeSet}; let scope = crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape { header: BasicBlockId(0), @@ -1274,7 +1331,7 @@ mod tests { #[test] fn test_capture_function_param_used_in_method_call() { - use crate::ast::{ASTNode, LiteralValue, Span, BinaryOperator}; + use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span}; // Simulate: fn parse_number(s, p) { loop(p < s.length()) { ch = s.charAt(p) } } // Expected: 's' should be captured (used in condition and body, not reassigned) @@ -1335,8 +1392,8 @@ mod tests { }, ]; - use std::collections::{BTreeSet, BTreeMap}; use crate::mir::BasicBlockId; + use std::collections::{BTreeMap, BTreeSet}; let scope = crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape { header: BasicBlockId(0), @@ -1364,7 +1421,7 @@ mod tests { #[test] fn test_capture_function_param_reassigned_rejected() { - use crate::ast::{ASTNode, LiteralValue, Span, BinaryOperator}; + use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span}; // Simulate: fn bad_func(x) { x = 5; loop(x < 10) { x = x + 1 } } // Expected: 'x' should NOT be captured (reassigned in function) @@ -1382,45 +1439,41 @@ mod tests { span: Span::unknown(), }); - let body = vec![ - ASTNode::Assignment { - target: Box::new(ASTNode::Variable { + let body = vec![ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "x".to_string(), + span: Span::unknown(), + }), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(ASTNode::Variable { name: "x".to_string(), span: Span::unknown(), }), - value: Box::new(ASTNode::BinaryOp { - operator: BinaryOperator::Add, - left: Box::new(ASTNode::Variable { - name: "x".to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(1), - span: Span::unknown(), - }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(1), span: Span::unknown(), }), span: Span::unknown(), - }, - ]; + }), + span: Span::unknown(), + }]; // fn_body includes reassignment before loop - let fn_body = vec![ - ASTNode::Assignment { - target: Box::new(ASTNode::Variable { - name: "x".to_string(), - span: Span::unknown(), - }), - value: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(5), - span: Span::unknown(), - }), + let fn_body = vec![ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "x".to_string(), span: Span::unknown(), - }, - ]; + }), + value: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(5), + span: Span::unknown(), + }), + span: Span::unknown(), + }]; - use std::collections::{BTreeSet, BTreeMap}; use crate::mir::BasicBlockId; + use std::collections::{BTreeMap, BTreeSet}; let scope = crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape { header: BasicBlockId(0), @@ -1444,7 +1497,7 @@ mod tests { #[test] fn test_capture_mixed_locals_and_params() { - use crate::ast::{ASTNode, LiteralValue, Span, BinaryOperator}; + use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span}; // Simulate: fn parse(s, len) { local digits = "0123"; loop(p < len) { ch = digits.indexOf(...); s.charAt(...) } } // Expected: 'len', 's', and 'digits' should all be captured @@ -1498,19 +1551,17 @@ mod tests { ]; // fn_body includes local declaration before loop - let fn_body = vec![ - ASTNode::Local { - variables: vec!["digits".to_string()], - initial_values: vec![Some(Box::new(ASTNode::Literal { - value: LiteralValue::String("0123".to_string()), - span: Span::unknown(), - }))], + let fn_body = vec![ASTNode::Local { + variables: vec!["digits".to_string()], + initial_values: vec![Some(Box::new(ASTNode::Literal { + value: LiteralValue::String("0123".to_string()), span: Span::unknown(), - }, - ]; + }))], + span: Span::unknown(), + }]; - use std::collections::{BTreeSet, BTreeMap}; use crate::mir::BasicBlockId; + use std::collections::{BTreeMap, BTreeSet}; let scope = crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape { header: BasicBlockId(0), diff --git a/src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs b/src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs index c3b02276..42930da9 100644 --- a/src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs +++ b/src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs @@ -68,16 +68,16 @@ impl TrimPatternInfo { /// - The actual host ValueId will be assigned during merge_joinir_mir_blocks /// - JoinInlineBoundary will handle the boundary mapping pub fn to_carrier_info(&self) -> crate::mir::join_ir::lowering::carrier_info::CarrierInfo { + use super::trim_loop_helper::TrimLoopHelper; use crate::mir::join_ir::lowering::carrier_info::CarrierInfo; use crate::mir::ValueId; - use super::trim_loop_helper::TrimLoopHelper; // Phase 171-C-4/5: Create CarrierInfo with promoted carrier as loop variable // and attach TrimLoopHelper for future lowering let mut carrier_info = CarrierInfo::with_carriers( - self.carrier_name.clone(), // "is_ch_match" becomes the loop variable - ValueId(0), // Placeholder (will be remapped) - vec![], // No additional carriers + self.carrier_name.clone(), // "is_ch_match" becomes the loop variable + ValueId(0), // Placeholder (will be remapped) + vec![], // No additional carriers ); // Phase 171-C-5: Attach TrimLoopHelper for pattern-specific lowering logic @@ -85,7 +85,9 @@ impl TrimPatternInfo { // Phase 229: Record promoted variable (no need for condition_aliases) // Dynamic resolution uses promoted_loopbodylocals + naming convention - carrier_info.promoted_loopbodylocals.push(self.var_name.clone()); + carrier_info + .promoted_loopbodylocals + .push(self.var_name.clone()); carrier_info } @@ -104,7 +106,7 @@ pub enum PromotionResult { /// 昇格不可: 理由を説明 CannotPromote { reason: String, - vars: Vec, // 問題の LoopBodyLocal + vars: Vec, // 問題の LoopBodyLocal }, } @@ -125,7 +127,10 @@ impl LoopBodyCarrierPromoter { use crate::mir::loop_pattern_detection::loop_condition_scope::CondVarScope; // 1. LoopBodyLocal を抽出 - let body_locals: Vec<&String> = request.cond_scope.vars.iter() + let body_locals: Vec<&String> = request + .cond_scope + .vars + .iter() .filter(|v| v.scope == CondVarScope::LoopBodyLocal) .map(|v| &v.name) .collect(); @@ -230,7 +235,11 @@ impl LoopBodyCarrierPromoter { } // If: then_body と else_body を探索 - ASTNode::If { then_body, else_body, .. } => { + ASTNode::If { + then_body, + else_body, + .. + } => { for stmt in then_body { worklist.push(stmt); } @@ -242,7 +251,9 @@ impl LoopBodyCarrierPromoter { } // Loop: body を探索(ネストループ) - ASTNode::Loop { body: loop_body, .. } => { + ASTNode::Loop { + body: loop_body, .. + } => { for stmt in loop_body { worklist.push(stmt); } @@ -250,7 +261,11 @@ impl LoopBodyCarrierPromoter { // Phase 171-impl-Trim: Handle Local with initial values // local ch = s.substring(...) - ASTNode::Local { variables, initial_values, .. } if initial_values.len() == variables.len() => { + ASTNode::Local { + variables, + initial_values, + .. + } if initial_values.len() == variables.len() => { for (i, var) in variables.iter().enumerate() { if var == var_name { if let Some(Some(init_expr)) = initial_values.get(i) { @@ -303,7 +318,12 @@ impl LoopBodyCarrierPromoter { while let Some(node) = worklist.pop() { match node { // BinaryOp: Or で分岐、Eq で比較 - ASTNode::BinaryOp { operator, left, right, .. } => { + ASTNode::BinaryOp { + operator, + left, + right, + .. + } => { match operator { // Or: 両側を探索 BinaryOperator::Or => { @@ -317,7 +337,11 @@ impl LoopBodyCarrierPromoter { if let ASTNode::Variable { name, .. } = left.as_ref() { if name == var_name { // right が String リテラル - if let ASTNode::Literal { value: LiteralValue::String(s), .. } = right.as_ref() { + if let ASTNode::Literal { + value: LiteralValue::String(s), + .. + } = right.as_ref() + { result.push(s.clone()); } } @@ -325,7 +349,11 @@ impl LoopBodyCarrierPromoter { // right が Variable で var_name に一致(逆順) if let ASTNode::Variable { name, .. } = right.as_ref() { if name == var_name { - if let ASTNode::Literal { value: LiteralValue::String(s), .. } = left.as_ref() { + if let ASTNode::Literal { + value: LiteralValue::String(s), + .. + } = left.as_ref() + { result.push(s.clone()); } } @@ -353,10 +381,10 @@ impl LoopBodyCarrierPromoter { mod tests { use super::*; use crate::ast::Span; - use crate::mir::BasicBlockId; use crate::mir::loop_pattern_detection::loop_condition_scope::{ CondVarScope, LoopConditionScope, }; + use crate::mir::BasicBlockId; use std::collections::{BTreeMap, BTreeSet}; fn minimal_scope() -> LoopScopeShape { @@ -489,9 +517,7 @@ mod tests { #[test] fn test_find_definition_in_body_simple() { // Test: local ch = s.substring(...) - let body = vec![ - assignment("ch", method_call("s", "substring")), - ]; + let body = vec![assignment("ch", method_call("s", "substring"))]; let result = LoopBodyCarrierPromoter::find_definition_in_body(&body, "ch"); @@ -507,20 +533,19 @@ mod tests { #[test] fn test_find_definition_in_body_nested_if() { // Test: Definition inside if-else block - let body = vec![ - ASTNode::If { - condition: Box::new(var_node("flag")), - then_body: vec![ - assignment("ch", method_call("s", "substring")), - ], - else_body: None, - span: Span::unknown(), - }, - ]; + let body = vec![ASTNode::If { + condition: Box::new(var_node("flag")), + then_body: vec![assignment("ch", method_call("s", "substring"))], + else_body: None, + span: Span::unknown(), + }]; let result = LoopBodyCarrierPromoter::find_definition_in_body(&body, "ch"); - assert!(result.is_some(), "Definition should be found inside if block"); + assert!( + result.is_some(), + "Definition should be found inside if block" + ); } #[test] @@ -528,8 +553,12 @@ mod tests { let substring_call = method_call("s", "substring"); let other_call = method_call("s", "length"); - assert!(LoopBodyCarrierPromoter::is_substring_method_call(&substring_call)); - assert!(!LoopBodyCarrierPromoter::is_substring_method_call(&other_call)); + assert!(LoopBodyCarrierPromoter::is_substring_method_call( + &substring_call + )); + assert!(!LoopBodyCarrierPromoter::is_substring_method_call( + &other_call + )); } #[test] @@ -547,10 +576,7 @@ mod tests { fn test_extract_equality_literals_or_chain() { // Test: ch == " " || ch == "\t" || ch == "\n" let cond = or_expr( - or_expr( - eq_cmp("ch", " "), - eq_cmp("ch", "\t"), - ), + or_expr(eq_cmp("ch", " "), eq_cmp("ch", "\t")), eq_cmp("ch", "\n"), ); @@ -569,7 +595,10 @@ mod tests { let result = LoopBodyCarrierPromoter::extract_equality_literals(&cond, "ch"); - assert!(result.is_empty(), "Should not extract literals for wrong variable"); + assert!( + result.is_empty(), + "Should not extract literals for wrong variable" + ); } #[test] @@ -582,14 +611,9 @@ mod tests { let scope = minimal_scope(); let cond_scope = cond_scope_with_body_local("ch"); - let loop_body = vec![ - assignment("ch", method_call("s", "substring")), - ]; + let loop_body = vec![assignment("ch", method_call("s", "substring"))]; - let break_cond = or_expr( - eq_cmp("ch", " "), - eq_cmp("ch", "\t"), - ); + let break_cond = or_expr(eq_cmp("ch", " "), eq_cmp("ch", "\t")); let request = PromotionRequest { scope: &scope, @@ -620,19 +644,11 @@ mod tests { let scope = minimal_scope(); let cond_scope = cond_scope_with_body_local("ch"); - let loop_body = vec![ - assignment("ch", method_call("s", "substring")), - ]; + let loop_body = vec![assignment("ch", method_call("s", "substring"))]; let break_cond = or_expr( - or_expr( - eq_cmp("ch", " "), - eq_cmp("ch", "\t"), - ), - or_expr( - eq_cmp("ch", "\n"), - eq_cmp("ch", "\r"), - ), + or_expr(eq_cmp("ch", " "), eq_cmp("ch", "\t")), + or_expr(eq_cmp("ch", "\n"), eq_cmp("ch", "\r")), ); let request = PromotionRequest { diff --git a/src/mir/loop_pattern_detection/loop_body_cond_promoter.rs b/src/mir/loop_pattern_detection/loop_body_cond_promoter.rs index 60456792..595a86ad 100644 --- a/src/mir/loop_pattern_detection/loop_body_cond_promoter.rs +++ b/src/mir/loop_pattern_detection/loop_body_cond_promoter.rs @@ -149,7 +149,7 @@ impl LoopBodyCondPromoter { /// - LoopBodyCondPromoter: Detection + metadata only (no code generation) pub fn try_promote_for_condition(req: ConditionPromotionRequest) -> ConditionPromotionResult { use crate::mir::loop_pattern_detection::loop_body_digitpos_promoter::{ - DigitPosPromotionRequest, DigitPosPromotionResult, DigitPosPromoter, + DigitPosPromoter, DigitPosPromotionRequest, DigitPosPromotionResult, }; use crate::mir::loop_pattern_detection::loop_condition_scope::CondVarScope; diff --git a/src/mir/loop_pattern_detection/loop_body_digitpos_promoter.rs b/src/mir/loop_pattern_detection/loop_body_digitpos_promoter.rs index 5979375d..474896c1 100644 --- a/src/mir/loop_pattern_detection/loop_body_digitpos_promoter.rs +++ b/src/mir/loop_pattern_detection/loop_body_digitpos_promoter.rs @@ -187,19 +187,21 @@ impl DigitPosPromoter { let bool_carrier_name = format!("is_{}", var_in_cond); // Extract the base name for integer carrier (e.g., "digit_pos" → "digit") let base_name = if var_in_cond.ends_with("_pos") { - &var_in_cond[..var_in_cond.len() - 4] // Remove "_pos" suffix + &var_in_cond[..var_in_cond.len() - 4] // Remove "_pos" suffix } else { var_in_cond.as_str() }; let int_carrier_name = format!("{}_value", base_name); - use crate::mir::join_ir::lowering::carrier_info::{CarrierVar, CarrierRole, CarrierInit}; + use crate::mir::join_ir::lowering::carrier_info::{ + CarrierInit, CarrierRole, CarrierVar, + }; // Boolean carrier (condition-only, for break) let promoted_carrier_bool = CarrierVar { name: bool_carrier_name.clone(), host_id: ValueId(0), // Placeholder (will be remapped) - join_id: None, // Will be allocated later + join_id: None, // Will be allocated later role: CarrierRole::ConditionOnly, // Phase 227: DigitPos is condition-only init: CarrierInit::BoolConst(false), // Phase 228: Initialize with false }; @@ -208,7 +210,7 @@ impl DigitPosPromoter { let promoted_carrier_int = CarrierVar { name: int_carrier_name.clone(), host_id: ValueId(0), // Placeholder (loop-local; no host slot) - join_id: None, // Will be allocated later + join_id: None, // Will be allocated later role: CarrierRole::LoopState, // Phase 247-EX: LoopState for accumulation init: CarrierInit::LoopLocalZero, // Derived in-loop carrier (no host binding) }; @@ -216,13 +218,15 @@ impl DigitPosPromoter { // Create CarrierInfo with a dummy loop_var_name (will be ignored during merge) let mut carrier_info = CarrierInfo::with_carriers( "__dummy_loop_var__".to_string(), // Placeholder, not used - ValueId(0), // Placeholder + ValueId(0), // Placeholder vec![promoted_carrier_bool, promoted_carrier_int], ); // Phase 229: Record promoted variable (no need for condition_aliases) // Dynamic resolution uses promoted_loopbodylocals + naming convention - carrier_info.promoted_loopbodylocals.push(var_in_cond.clone()); + carrier_info + .promoted_loopbodylocals + .push(var_in_cond.clone()); eprintln!( "[digitpos_promoter] Phase 247-EX: A-4 DigitPos pattern promoted: {} → {} (bool) + {} (i64)", @@ -335,9 +339,7 @@ impl DigitPosPromoter { /// Handles: `if digit_pos < 0`, `if digit_pos >= 0`, etc. fn extract_comparison_var(cond: &ASTNode) -> Option { match cond { - ASTNode::BinaryOp { - operator, left, .. - } => { + ASTNode::BinaryOp { operator, left, .. } => { // Check if it's a comparison operator (not equality) match operator { BinaryOperator::Less diff --git a/src/mir/loop_pattern_detection/loop_condition_scope.rs b/src/mir/loop_pattern_detection/loop_condition_scope.rs index 338ea828..b6cab677 100644 --- a/src/mir/loop_pattern_detection/loop_condition_scope.rs +++ b/src/mir/loop_pattern_detection/loop_condition_scope.rs @@ -46,7 +46,9 @@ impl LoopConditionScope { /// Check if this scope contains any loop-body-local variables pub fn has_loop_body_local(&self) -> bool { - self.vars.iter().any(|v| v.scope == CondVarScope::LoopBodyLocal) + self.vars + .iter() + .any(|v| v.scope == CondVarScope::LoopBodyLocal) } /// Check if all variables in this scope are in the allowed set diff --git a/src/mir/loop_pattern_detection/mod.rs b/src/mir/loop_pattern_detection/mod.rs index 745df5a3..a001351c 100644 --- a/src/mir/loop_pattern_detection/mod.rs +++ b/src/mir/loop_pattern_detection/mod.rs @@ -19,8 +19,8 @@ //! //! Reference: docs/private/roadmap2/phases/phase-188-joinir-loop-pattern-expansion/design.md -use crate::mir::loop_form::LoopForm; use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; +use crate::mir::loop_form::LoopForm; // ============================================================================ // Pattern Classification System (Phase 194+) @@ -142,7 +142,8 @@ pub struct LoopFeatures { /// Contains UpdateKind (CounterLike/AccumulationLike/Other) for each carrier. /// Used by CaseALoweringShape for more precise shape detection. /// None if carrier names are not available. - pub update_summary: Option, + pub update_summary: + Option, } impl LoopFeatures { @@ -196,7 +197,10 @@ impl LoopFeatures { /// /// # Returns /// * `LoopFeatures` - Feature vector for pattern classification -pub(crate) fn extract_features(loop_form: &LoopForm, scope: Option<&LoopScopeShape>) -> LoopFeatures { +pub(crate) fn extract_features( + loop_form: &LoopForm, + scope: Option<&LoopScopeShape>, +) -> LoopFeatures { // Phase 194: Basic feature extraction from LoopForm let has_break = !loop_form.break_targets.is_empty(); let has_continue = !loop_form.continue_targets.is_empty(); @@ -219,7 +223,9 @@ pub(crate) fn extract_features(loop_form: &LoopForm, scope: Option<&LoopScopeSha // Note: carriers is BTreeSet, so each item is already a String let update_summary = scope.map(|s| { let carrier_names: Vec = s.carriers.iter().cloned().collect(); - crate::mir::join_ir::lowering::loop_update_summary::analyze_loop_updates_by_name(&carrier_names) + crate::mir::join_ir::lowering::loop_update_summary::analyze_loop_updates_by_name( + &carrier_names, + ) }); LoopFeatures { @@ -275,7 +281,11 @@ pub fn classify(features: &LoopFeatures) -> LoopPatternKind { // Pattern 3: If-PHI (check before Pattern 1) // Phase 212.5: Structural if detection - route to P3 if has_if && carrier_count >= 1 - if features.has_if && features.carrier_count >= 1 && !features.has_break && !features.has_continue { + if features.has_if + && features.carrier_count >= 1 + && !features.has_break + && !features.has_continue + { return LoopPatternKind::Pattern3IfPhi; } @@ -326,10 +336,7 @@ pub fn classify_with_diagnosis(features: &LoopFeatures) -> (LoopPatternKind, Str "Simple while loop with no special control flow".to_string() } LoopPatternKind::Unknown => { - format!( - "Unknown pattern: {}", - features.debug_stats() - ) + format!("Unknown pattern: {}", features.debug_stats()) } }; @@ -666,8 +673,8 @@ fn has_simple_condition(_loop_form: &LoopForm) -> bool { mod tests; // Phase 170-D: Loop Condition Scope Analysis Boxes -pub mod loop_condition_scope; pub mod condition_var_analyzer; +pub mod loop_condition_scope; // Phase 170-ultrathink: Error Message Utilities pub mod error_messages; diff --git a/src/mir/loop_pattern_detection/tests.rs b/src/mir/loop_pattern_detection/tests.rs index 8994e535..bbdca0ee 100644 --- a/src/mir/loop_pattern_detection/tests.rs +++ b/src/mir/loop_pattern_detection/tests.rs @@ -40,8 +40,15 @@ fn assignment(target: ASTNode, value: ASTNode) -> ASTNode { fn has_continue(node: &ASTNode) -> bool { match node { ASTNode::Continue { .. } => true, - ASTNode::If { then_body, else_body, .. } => { - then_body.iter().any(has_continue) || else_body.as_ref().map_or(false, |b| b.iter().any(has_continue)) + ASTNode::If { + then_body, + else_body, + .. + } => { + then_body.iter().any(has_continue) + || else_body + .as_ref() + .map_or(false, |b| b.iter().any(has_continue)) } ASTNode::Loop { body, .. } => body.iter().any(has_continue), _ => false, @@ -51,8 +58,15 @@ fn has_continue(node: &ASTNode) -> bool { fn has_break(node: &ASTNode) -> bool { match node { ASTNode::Break { .. } => true, - ASTNode::If { then_body, else_body, .. } => { - then_body.iter().any(has_break) || else_body.as_ref().map_or(false, |b| b.iter().any(has_break)) + ASTNode::If { + then_body, + else_body, + .. + } => { + then_body.iter().any(has_break) + || else_body + .as_ref() + .map_or(false, |b| b.iter().any(has_break)) } ASTNode::Loop { body, .. } => body.iter().any(has_break), _ => false, @@ -69,7 +83,11 @@ fn carrier_count(body: &[ASTNode]) -> usize { for n in nodes { match n { ASTNode::Assignment { .. } => c += 1, - ASTNode::If { then_body, else_body, .. } => { + ASTNode::If { + then_body, + else_body, + .. + } => { c += count(then_body); if let Some(else_body) = else_body { c += count(else_body); @@ -80,7 +98,11 @@ fn carrier_count(body: &[ASTNode]) -> usize { } c } - if count(body) > 0 { 1 } else { 0 } + if count(body) > 0 { + 1 + } else { + 0 + } } fn classify_body(body: &[ASTNode]) -> LoopPatternKind { @@ -102,7 +124,10 @@ fn classify_body(body: &[ASTNode]) -> LoopPatternKind { #[test] fn pattern1_simple_while_is_detected() { // loop(i < len) { i = i + 1 } - let body = vec![assignment(var("i"), bin(BinaryOperator::Add, var("i"), lit_i(1)))]; + let body = vec![assignment( + var("i"), + bin(BinaryOperator::Add, var("i"), lit_i(1)), + )]; let kind = classify_body(&body); assert_eq!(kind, LoopPatternKind::Pattern1SimpleWhile); } @@ -211,7 +236,7 @@ fn test_atoi_loop_classified_as_pattern2() { let mul_expr = bin(BinaryOperator::Multiply, var("result"), lit_i(10)); let result_update = assignment( var("result"), - bin(BinaryOperator::Add, mul_expr, var("digit_pos")) + bin(BinaryOperator::Add, mul_expr, var("digit_pos")), ); // i = i + 1 @@ -229,6 +254,9 @@ fn test_atoi_loop_classified_as_pattern2() { ]; let kind = classify_body(&body); - assert_eq!(kind, LoopPatternKind::Pattern2Break, - "_atoi loop should be classified as Pattern2 (Break) due to if-break structure"); + assert_eq!( + kind, + LoopPatternKind::Pattern2Break, + "_atoi loop should be classified as Pattern2 (Break) due to if-break structure" + ); } diff --git a/src/mir/loop_pattern_detection/trim_loop_helper.rs b/src/mir/loop_pattern_detection/trim_loop_helper.rs index ab0564c8..202dba12 100644 --- a/src/mir/loop_pattern_detection/trim_loop_helper.rs +++ b/src/mir/loop_pattern_detection/trim_loop_helper.rs @@ -275,7 +275,12 @@ mod tests { let helper = TrimLoopHelper { original_var: "ch".to_string(), carrier_name: "is_whitespace".to_string(), - whitespace_chars: vec![" ".to_string(), "\t".to_string(), "\n".to_string(), "\r".to_string()], + whitespace_chars: vec![ + " ".to_string(), + "\t".to_string(), + "\n".to_string(), + "\r".to_string(), + ], }; assert_eq!(helper.whitespace_count(), 4); diff --git a/src/mir/mod.rs b/src/mir/mod.rs index 769efc0b..cbc82b8d 100644 --- a/src/mir/mod.rs +++ b/src/mir/mod.rs @@ -12,17 +12,18 @@ pub mod builder; pub mod definitions; // Unified MIR definitions (MirCall, Callee, etc.) pub mod effect; pub mod function; +pub mod if_in_loop_phi; // Phase 187-2: Minimal if-in-loop PHI emitter (extracted from loop_builder) pub mod instruction; pub mod instruction_introspection; // Introspection helpers for tests (instruction names) pub mod instruction_kinds; // small kind-specific metadata (Const/BinOp) pub mod loop_api; // Minimal LoopBuilder facade (adapter-ready) -pub mod if_in_loop_phi; // Phase 187-2: Minimal if-in-loop PHI emitter (extracted from loop_builder) pub mod naming; // Static box / entry naming rules(NamingBox) pub mod optimizer; pub mod ssot; // Shared helpers (SSOT) for instruction lowering pub mod types; // core MIR enums (ConstValue, Ops, MirType) pub mod utils; // Phase 15 control flow utilities for root treatment // pub mod lowerers; // reserved: Stage-3 loop lowering (while/for-range) +pub mod cfg_extractor; // Phase 154: CFG extraction for hako_check pub mod control_form; pub mod function_emission; // FunctionEmissionBox(MirFunction直編集の発行ヘルパ) pub mod hints; // scaffold: zero-cost guidance (no-op) @@ -31,7 +32,6 @@ pub mod join_ir_ops; // Phase 27.8: JoinIR 命令意味箱(ops box) pub mod join_ir_runner; // Phase 27.2: JoinIR 実行器(実験用) pub mod join_ir_vm_bridge; // Phase 27-shortterm S-4: JoinIR → Rust VM ブリッジ pub mod join_ir_vm_bridge_dispatch; // Phase 30 F-4.4: JoinIR VM ブリッジ dispatch helper -pub mod cfg_extractor; // Phase 154: CFG extraction for hako_check pub mod loop_form; // ControlForm::LoopShape の薄いエイリアス pub mod loop_pattern_detection; // Phase 188: Loop pattern detection for JoinIR lowering pub mod optimizer_passes; // optimizer passes (normalize/diagnostics) diff --git a/src/mir/optimizer_passes/boxfield.rs b/src/mir/optimizer_passes/boxfield.rs index 13165956..12e63e29 100644 --- a/src/mir/optimizer_passes/boxfield.rs +++ b/src/mir/optimizer_passes/boxfield.rs @@ -20,11 +20,19 @@ pub fn optimize_boxfield_operations( let mut i = 0usize; while i < block.instructions.len() { // Get span for current instruction (or unknown if missing) - let span_i = block.instruction_spans.get(i).copied().unwrap_or_else(Span::unknown); + let span_i = block + .instruction_spans + .get(i) + .copied() + .unwrap_or_else(Span::unknown); // Look ahead for simple store-followed-by-load on same box/index if i + 1 < block.instructions.len() { - let span_i1 = block.instruction_spans.get(i + 1).copied().unwrap_or_else(Span::unknown); + let span_i1 = block + .instruction_spans + .get(i + 1) + .copied() + .unwrap_or_else(Span::unknown); match (&block.instructions[i], &block.instructions[i + 1]) { ( I::BoxCall { diff --git a/src/mir/passes/cse.rs b/src/mir/passes/cse.rs index b3c1c9df..138e31bc 100644 --- a/src/mir/passes/cse.rs +++ b/src/mir/passes/cse.rs @@ -78,7 +78,9 @@ fn instruction_key(i: &MirInstruction) -> String { MirInstruction::Compare { op, lhs, rhs, .. } => { format!("cmp_{:?}_{}_{}", op, lhs.as_u32(), rhs.as_u32()) } - MirInstruction::Call { callee, func, args, .. } => { + MirInstruction::Call { + callee, func, args, .. + } => { let args_str = args .iter() .map(|v| v.as_u32().to_string()) @@ -101,8 +103,10 @@ fn instruction_key(i: &MirInstruction) -> String { let recv_str = receiver .map(|r| r.as_u32().to_string()) .unwrap_or_else(|| "static".to_string()); - format!("call_method_{}.{}_{}_{}", - box_name, method, recv_str, args_str) + format!( + "call_method_{}.{}_{}_{}", + box_name, method, recv_str, args_str + ) } Callee::Value(v) => { format!("call_value_{}_{}", v.as_u32(), args_str) diff --git a/src/mir/phi_core/copy_type_propagator.rs b/src/mir/phi_core/copy_type_propagator.rs index 38ee400d..b2060822 100644 --- a/src/mir/phi_core/copy_type_propagator.rs +++ b/src/mir/phi_core/copy_type_propagator.rs @@ -44,7 +44,10 @@ impl CopyTypePropagator { /// # 戻り値 /// /// 伝播された型の数 - pub fn propagate(function: &MirFunction, value_types: &mut BTreeMap) -> usize { + pub fn propagate( + function: &MirFunction, + value_types: &mut BTreeMap, + ) -> usize { let mut total_propagated = 0usize; // 固定点ループ: 変化がなくなるまで反復 @@ -91,10 +94,7 @@ impl CopyTypePropagator { propagated += 1; if std::env::var("NYASH_COPY_PROP_TRACE").is_ok() { - eprintln!( - "[copy_prop] {:?} <- {:?} : {:?}", - dst, src, src_type - ); + eprintln!("[copy_prop] {:?} <- {:?} : {:?}", dst, src, src_type); } } } @@ -109,7 +109,7 @@ impl CopyTypePropagator { #[cfg(test)] mod tests { use super::*; - use crate::mir::{BasicBlock, BasicBlockId, MirFunction, FunctionSignature, EffectMask}; + use crate::mir::{BasicBlock, BasicBlockId, EffectMask, FunctionSignature, MirFunction}; fn make_test_function() -> MirFunction { let sig = FunctionSignature { diff --git a/src/mir/phi_core/loop_snapshot_merge.rs b/src/mir/phi_core/loop_snapshot_merge.rs index 262ae825..56984d8d 100644 --- a/src/mir/phi_core/loop_snapshot_merge.rs +++ b/src/mir/phi_core/loop_snapshot_merge.rs @@ -87,10 +87,18 @@ impl LoopSnapshotMergeBox { let definitions = build_definitions(header_id, header_vals, exit_snapshots); if debug { - crate::runtime::get_global_ring0().log.debug("[Option C] merge_exit_with_classification called"); - crate::runtime::get_global_ring0().log.debug(&format!("[Option C] exit_preds: {:?}", exit_preds)); - crate::runtime::get_global_ring0().log.debug(&format!("[Option C] pinned_vars: {:?}", pinned_vars)); - crate::runtime::get_global_ring0().log.debug(&format!("[Option C] carrier_vars: {:?}", carrier_vars)); + crate::runtime::get_global_ring0() + .log + .debug("[Option C] merge_exit_with_classification called"); + crate::runtime::get_global_ring0() + .log + .debug(&format!("[Option C] exit_preds: {:?}", exit_preds)); + crate::runtime::get_global_ring0() + .log + .debug(&format!("[Option C] pinned_vars: {:?}", pinned_vars)); + crate::runtime::get_global_ring0() + .log + .debug(&format!("[Option C] carrier_vars: {:?}", carrier_vars)); } // [LoopForm] Case A/B 分岐: @@ -126,7 +134,10 @@ impl LoopSnapshotMergeBox { var_name, class, needs_exit_phi )); if let Some(defining_blocks) = definitions.get(&var_name) { - crate::runtime::get_global_ring0().log.debug(&format!("[Option C] defining_blocks: {:?}", defining_blocks)); + crate::runtime::get_global_ring0().log.debug(&format!( + "[Option C] defining_blocks: {:?}", + defining_blocks + )); } } diff --git a/src/mir/phi_core/loopform/builder_core.rs b/src/mir/phi_core/loopform/builder_core.rs index ec19d6fe..2b285e45 100644 --- a/src/mir/phi_core/loopform/builder_core.rs +++ b/src/mir/phi_core/loopform/builder_core.rs @@ -147,7 +147,8 @@ impl LoopFormBuilder { for (i, (bb, snap)) in exit_snapshots.iter().enumerate() { eprintln!( "[DEBUG/exit_phi] snapshot[{}]: block = {:?}, num_vars = {}", - i, bb, + i, + bb, snap.len() ); } @@ -311,7 +312,8 @@ impl LoopFormBuilder { if debug { eprintln!( "[DEBUG/exit_phi] Creating PHI {:?} for var '{}' with {} inputs", - phi_id, var_name, + phi_id, + var_name, final_inputs.len() ); for (bb, val) in &final_inputs { diff --git a/src/mir/phi_core/loopform/exit_phi.rs b/src/mir/phi_core/loopform/exit_phi.rs index a523503b..0cd84eec 100644 --- a/src/mir/phi_core/loopform/exit_phi.rs +++ b/src/mir/phi_core/loopform/exit_phi.rs @@ -8,8 +8,8 @@ //! This module extracts the exit PHI building logic from loopform_builder.rs //! to improve modularity and testability. -use crate::mir::{BasicBlockId, ValueId}; use crate::mir::control_form::ControlForm; +use crate::mir::{BasicBlockId, ValueId}; use std::collections::BTreeMap; /// Exit PHI builder (currently a delegation wrapper) diff --git a/src/mir/phi_core/loopform/mod.rs b/src/mir/phi_core/loopform/mod.rs index e0c4ee2d..b49b5e5f 100644 --- a/src/mir/phi_core/loopform/mod.rs +++ b/src/mir/phi_core/loopform/mod.rs @@ -61,24 +61,22 @@ //! Phase 193: Complete 4-pass architecture explicit structure // Core modules -pub mod context; -pub mod variable_models; -pub mod utils; -pub mod exit_phi; pub mod builder_core; +pub mod context; +pub mod exit_phi; pub mod passes; +pub mod utils; +pub mod variable_models; // Re-export main types for backward compatibility pub use builder_core::{LoopFormBuilder, LoopFormOps}; pub use context::LoopFormContext; -pub use variable_models::{CarrierVariable, PinnedVariable, LoopBypassFlags}; +pub use variable_models::{CarrierVariable, LoopBypassFlags, PinnedVariable}; // Re-export utility functions pub use utils::{ - is_loopform_debug_enabled, - get_loop_bypass_flags, + get_loop_bypass_flags, is_joinir_exit_bypass_target, is_loopform_debug_enabled, joinir_exit_bypass_enabled, - is_joinir_exit_bypass_target, }; // Re-export exit PHI function for backward compatibility diff --git a/src/mir/phi_core/loopform/passes/pass1_discovery.rs b/src/mir/phi_core/loopform/passes/pass1_discovery.rs index f423fbc3..2a13fe5e 100644 --- a/src/mir/phi_core/loopform/passes/pass1_discovery.rs +++ b/src/mir/phi_core/loopform/passes/pass1_discovery.rs @@ -31,7 +31,7 @@ //! // - pinned: [PinnedVariable { name: "y", param: 2, copy: 5, phi: 6 }] //! ``` -use crate::mir::{ValueId}; +use crate::mir::ValueId; use std::collections::BTreeMap; use crate::mir::phi_core::loopform::builder_core::{LoopFormBuilder, LoopFormOps}; @@ -57,11 +57,15 @@ pub fn prepare_structure( "[loopform/prepare] === START prepare_structure() === {} variables", current_vars.len() )); - crate::runtime::get_global_ring0().log.debug("[loopform/prepare] Full variable list:"); + crate::runtime::get_global_ring0() + .log + .debug("[loopform/prepare] Full variable list:"); let mut sorted_vars: Vec<_> = current_vars.iter().collect(); sorted_vars.sort_by_key(|(name, _)| name.as_str()); for (name, value) in &sorted_vars { - crate::runtime::get_global_ring0().log.debug(&format!("[loopform/prepare] - {} = {:?}", name, value)); + crate::runtime::get_global_ring0() + .log + .debug(&format!("[loopform/prepare] - {} = {:?}", name, value)); } } @@ -146,10 +150,21 @@ pub fn prepare_structure( } if debug_enabled { - crate::runtime::get_global_ring0().log.debug("[loopform/prepare] === SUMMARY ==="); - crate::runtime::get_global_ring0().log.debug(&format!("[loopform/prepare] Total vars: {}", current_vars.len())); - crate::runtime::get_global_ring0().log.debug(&format!("[loopform/prepare] Pinned (params): {}", param_count)); - crate::runtime::get_global_ring0().log.debug(&format!("[loopform/prepare] Carriers (locals): {}", carrier_count)); + crate::runtime::get_global_ring0() + .log + .debug("[loopform/prepare] === SUMMARY ==="); + crate::runtime::get_global_ring0().log.debug(&format!( + "[loopform/prepare] Total vars: {}", + current_vars.len() + )); + crate::runtime::get_global_ring0().log.debug(&format!( + "[loopform/prepare] Pinned (params): {}", + param_count + )); + crate::runtime::get_global_ring0().log.debug(&format!( + "[loopform/prepare] Carriers (locals): {}", + carrier_count + )); } Ok(()) diff --git a/src/mir/phi_core/loopform/passes/pass3_header_phi.rs b/src/mir/phi_core/loopform/passes/pass3_header_phi.rs index 6b4ce8d9..626d9365 100644 --- a/src/mir/phi_core/loopform/passes/pass3_header_phi.rs +++ b/src/mir/phi_core/loopform/passes/pass3_header_phi.rs @@ -80,8 +80,7 @@ pub fn emit_header_phis( // Extract the pin counter and update all lower levels if let Some(idx) = pinned.name.find("$") { if let Some(end_idx) = pinned.name[idx + 1..].find("$") { - if let Ok(counter) = pinned.name[idx + 1..idx + 1 + end_idx].parse::() - { + if let Ok(counter) = pinned.name[idx + 1..idx + 1 + end_idx].parse::() { // Update all previous pin levels (1 through counter-1) for i in 1..counter { let alias = format!("__pin${}$@recv", i); diff --git a/src/mir/phi_core/loopform/variable_models.rs b/src/mir/phi_core/loopform/variable_models.rs index 2b184f27..9fa4c1ba 100644 --- a/src/mir/phi_core/loopform/variable_models.rs +++ b/src/mir/phi_core/loopform/variable_models.rs @@ -14,10 +14,10 @@ use crate::mir::ValueId; #[derive(Debug, Clone)] pub struct CarrierVariable { pub name: String, - pub init_value: ValueId, // Initial value from preheader (local variable) - pub preheader_copy: ValueId, // Copy allocated in preheader block - pub header_phi: ValueId, // PHI node allocated in header block - pub latch_value: ValueId, // Updated value computed in latch (set during sealing) + pub init_value: ValueId, // Initial value from preheader (local variable) + pub preheader_copy: ValueId, // Copy allocated in preheader block + pub header_phi: ValueId, // PHI node allocated in header block + pub latch_value: ValueId, // Updated value computed in latch (set during sealing) } /// A pinned variable: not modified in loop body (loop-invariant, typically parameters) @@ -27,9 +27,9 @@ pub struct CarrierVariable { #[derive(Debug, Clone)] pub struct PinnedVariable { pub name: String, - pub param_value: ValueId, // Original parameter or loop-invariant value - pub preheader_copy: ValueId, // Copy allocated in preheader block - pub header_phi: ValueId, // PHI node allocated in header block + pub param_value: ValueId, // Original parameter or loop-invariant value + pub preheader_copy: ValueId, // Copy allocated in preheader block + pub header_phi: ValueId, // PHI node allocated in header block } /// Phase 27.4-C Refactor: JoinIR Loop φ bypass flags diff --git a/src/mir/utils/control_flow.rs b/src/mir/utils/control_flow.rs index 0ac819a8..ebbe5d13 100644 --- a/src/mir/utils/control_flow.rs +++ b/src/mir/utils/control_flow.rs @@ -115,7 +115,6 @@ pub fn execute_statement_with_termination_check( #[cfg(test)] mod tests { - // ユニットテスト(将来追加) // - 終端検出の正確性 diff --git a/src/mir/utils/phi_helpers.rs b/src/mir/utils/phi_helpers.rs index 7a42cced..5b3fdb4a 100644 --- a/src/mir/utils/phi_helpers.rs +++ b/src/mir/utils/phi_helpers.rs @@ -274,7 +274,6 @@ impl MirBuilder { #[cfg(test)] mod tests { - // ユニットテストは実際のMirBuilder構造が必要なため、 // 統合テストでの検証を推奨(smoke testsで実証) diff --git a/src/providers/ring1/file/core_ro.rs b/src/providers/ring1/file/core_ro.rs index 832fb37b..f622d3bc 100644 --- a/src/providers/ring1/file/core_ro.rs +++ b/src/providers/ring1/file/core_ro.rs @@ -49,7 +49,9 @@ impl FileIo for CoreRoFileIo { fn write(&self, _text: &str) -> FileResult<()> { // CoreRoFileIo is read-only, write is not supported - Err(FileError::Unsupported("CoreRoFileIo is read-only".to_string())) + Err(FileError::Unsupported( + "CoreRoFileIo is read-only".to_string(), + )) } fn close(&self) -> FileResult<()> { @@ -67,9 +69,7 @@ impl FileIo for CoreRoFileIo { fn exists(&self) -> bool { let path_lock = self.path.read().unwrap(); match path_lock.as_ref() { - Some(path_str) => { - Path::new(path_str).exists() - } + Some(path_str) => Path::new(path_str).exists(), None => false, } } @@ -87,9 +87,9 @@ impl FileIo for CoreRoFileIo { size: meta.len(), }) } - None => { - Err(FileError::Io("No file path set. Call open() first.".to_string())) - } + None => Err(FileError::Io( + "No file path set. Call open() first.".to_string(), + )), } } @@ -102,9 +102,9 @@ impl FileIo for CoreRoFileIo { .map_err(|e| FileError::Io(format!("Canonicalize failed: {}", e)))?; Ok(canonical.display().to_string()) } - None => { - Err(FileError::Io("No file path set. Call open() first.".to_string())) - } + None => Err(FileError::Io( + "No file path set. Call open() first.".to_string(), + )), } } } diff --git a/src/providers/ring1/file/mod.rs b/src/providers/ring1/file/mod.rs index b700de1c..7f283520 100644 --- a/src/providers/ring1/file/mod.rs +++ b/src/providers/ring1/file/mod.rs @@ -1,3 +1,3 @@ pub mod core_ro; -pub mod ring0_fs_fileio; -pub mod nofs_fileio; // Phase 109: NoFs profile stub +pub mod nofs_fileio; +pub mod ring0_fs_fileio; // Phase 109: NoFs profile stub diff --git a/src/providers/ring1/file/nofs_fileio.rs b/src/providers/ring1/file/nofs_fileio.rs index 43b96bf3..61ada0fa 100644 --- a/src/providers/ring1/file/nofs_fileio.rs +++ b/src/providers/ring1/file/nofs_fileio.rs @@ -36,19 +36,27 @@ impl FileIo for NoFsFileIo { } fn open(&self, _path: &str) -> FileResult<()> { - Err(FileError::Unsupported("FileBox disabled in NoFs profile".to_string())) + Err(FileError::Unsupported( + "FileBox disabled in NoFs profile".to_string(), + )) } fn read(&self) -> FileResult { - Err(FileError::Unsupported("FileBox disabled in NoFs profile".to_string())) + Err(FileError::Unsupported( + "FileBox disabled in NoFs profile".to_string(), + )) } fn write(&self, _text: &str) -> FileResult<()> { - Err(FileError::Unsupported("FileBox disabled in NoFs profile".to_string())) + Err(FileError::Unsupported( + "FileBox disabled in NoFs profile".to_string(), + )) } fn close(&self) -> FileResult<()> { - Err(FileError::Unsupported("FileBox disabled in NoFs profile".to_string())) + Err(FileError::Unsupported( + "FileBox disabled in NoFs profile".to_string(), + )) } fn as_any(&self) -> &dyn std::any::Any { @@ -64,13 +72,13 @@ impl FileIo for NoFsFileIo { fn stat(&self) -> FileResult { Err(FileError::Unsupported( - "FileSystem operations disabled in no-fs profile".to_string() + "FileSystem operations disabled in no-fs profile".to_string(), )) } fn canonicalize(&self) -> FileResult { Err(FileError::Unsupported( - "FileSystem operations disabled in no-fs profile".to_string() + "FileSystem operations disabled in no-fs profile".to_string(), )) } } @@ -125,7 +133,10 @@ mod tests { fn test_nofs_fileio_exists() { let fileio = NoFsFileIo; // NoFsFileIo.exists() should always return false - assert!(!fileio.exists(), "NoFsFileIo.exists() should always return false"); + assert!( + !fileio.exists(), + "NoFsFileIo.exists() should always return false" + ); } #[test] @@ -133,7 +144,10 @@ mod tests { let fileio = NoFsFileIo; let result = fileio.stat(); assert!(result.is_err(), "stat() should return error"); - assert!(result.unwrap_err().to_string().contains("unsupported"), "should contain 'unsupported'"); + assert!( + result.unwrap_err().to_string().contains("unsupported"), + "should contain 'unsupported'" + ); } #[test] @@ -141,6 +155,9 @@ mod tests { let fileio = NoFsFileIo; let result = fileio.canonicalize(); assert!(result.is_err(), "canonicalize() should return error"); - assert!(result.unwrap_err().to_string().contains("unsupported"), "should contain 'unsupported'"); + assert!( + result.unwrap_err().to_string().contains("unsupported"), + "should contain 'unsupported'" + ); } } diff --git a/src/providers/ring1/file/ring0_fs_fileio.rs b/src/providers/ring1/file/ring0_fs_fileio.rs index f28aafd7..64db62bd 100644 --- a/src/providers/ring1/file/ring0_fs_fileio.rs +++ b/src/providers/ring1/file/ring0_fs_fileio.rs @@ -50,12 +50,14 @@ impl Ring0FsFileIo { match current_path.as_ref() { Some(path) => { let path_obj = Path::new(path); - self.ring0.fs.metadata(path_obj) + self.ring0 + .fs + .metadata(path_obj) .map_err(|e| FileError::Io(format!("Metadata failed: {}", e))) } - None => { - Err(FileError::Io("No file path set. Call open() first.".to_string())) - } + None => Err(FileError::Io( + "No file path set. Call open() first.".to_string(), + )), } } } @@ -63,7 +65,10 @@ impl Ring0FsFileIo { impl FileIo for Ring0FsFileIo { fn caps(&self) -> FileCaps { // Phase 108: Read/write support - FileCaps { read: true, write: true } + FileCaps { + read: true, + write: true, + } } fn open(&self, path: &str) -> FileResult<()> { @@ -72,7 +77,7 @@ impl FileIo for Ring0FsFileIo { // Phase 107 Design Decision: One file at a time if current_path.is_some() { return Err(FileError::Io( - "File already open. Call close() before opening another file.".to_string() + "File already open. Call close() before opening another file.".to_string(), )); } @@ -94,12 +99,14 @@ impl FileIo for Ring0FsFileIo { Some(path) => { // Delegate to Ring0.FsApi (UTF-8 handling is done by FsApi) let path_obj = Path::new(path); - self.ring0.fs.read_to_string(path_obj) + self.ring0 + .fs + .read_to_string(path_obj) .map_err(|e| FileError::Io(format!("Read failed: {}", e))) } - None => { - Err(FileError::Io("No file is currently open. Call open() first.".to_string())) - } + None => Err(FileError::Io( + "No file is currently open. Call open() first.".to_string(), + )), } } @@ -115,33 +122,36 @@ impl FileIo for Ring0FsFileIo { match mode.as_str() { "w" => { // Truncate mode: overwrite existing file - self.ring0.fs.write_all(path_obj, text.as_bytes()) + self.ring0 + .fs + .write_all(path_obj, text.as_bytes()) .map_err(|e| FileError::Io(format!("Write failed: {}", e))) } "a" => { // Append mode: append to end of file - self.ring0.fs.append_all(path_obj, text.as_bytes()) + self.ring0 + .fs + .append_all(path_obj, text.as_bytes()) .map_err(|e| FileError::Io(format!("Append failed: {}", e))) } "r" => { // Read-only mode: cannot write Err(FileError::Unsupported( - "Cannot write in read-only mode".to_string() - )) - } - _ => { - Err(FileError::Unsupported( - format!("Unsupported mode: {}", mode) + "Cannot write in read-only mode".to_string(), )) } + _ => Err(FileError::Unsupported(format!( + "Unsupported mode: {}", + mode + ))), } } - (None, _) => { - Err(FileError::Io("No file is currently open. Call open() first.".to_string())) - } - (Some(_), None) => { - Err(FileError::Io("File mode not set. Internal error.".to_string())) - } + (None, _) => Err(FileError::Io( + "No file is currently open. Call open() first.".to_string(), + )), + (Some(_), None) => Err(FileError::Io( + "File mode not set. Internal error.".to_string(), + )), } } @@ -175,7 +185,10 @@ impl FileIo for Ring0FsFileIo { match path_lock.as_ref() { Some(path_str) => { let path_obj = Path::new(path_str); - let meta = self.ring0.fs.metadata(path_obj) + let meta = self + .ring0 + .fs + .metadata(path_obj) .map_err(|e| FileError::Io(format!("Metadata failed: {}", e)))?; Ok(crate::boxes::file::provider::FileStat { is_file: meta.is_file, @@ -183,9 +196,9 @@ impl FileIo for Ring0FsFileIo { size: meta.len, }) } - None => { - Err(FileError::Io("No file path set. Call open() first.".to_string())) - } + None => Err(FileError::Io( + "No file path set. Call open() first.".to_string(), + )), } } @@ -194,13 +207,16 @@ impl FileIo for Ring0FsFileIo { match path_lock.as_ref() { Some(path_str) => { let path_obj = Path::new(path_str); - let canonical = self.ring0.fs.canonicalize(path_obj) + let canonical = self + .ring0 + .fs + .canonicalize(path_obj) .map_err(|e| FileError::Io(format!("Canonicalize failed: {}", e)))?; Ok(canonical.display().to_string()) } - None => { - Err(FileError::Io("No file path set. Call open() first.".to_string())) - } + None => Err(FileError::Io( + "No file path set. Call open() first.".to_string(), + )), } } } @@ -280,7 +296,10 @@ mod tests { // Read without open should fail let result = fileio.read(); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("No file is currently open")); + assert!(result + .unwrap_err() + .to_string() + .contains("No file is currently open")); } #[test] @@ -361,7 +380,10 @@ mod tests { // Write without open should fail let result = fileio.write("test"); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("No file is currently open")); + assert!(result + .unwrap_err() + .to_string() + .contains("No file is currently open")); } // ===== Phase 114: Metadata operation tests ===== @@ -407,7 +429,10 @@ mod tests { cleanup_test_file(test_path); // After file is deleted, exists() with no path set should return false - assert!(!fileio.exists(), "exists() should return false when no path set"); + assert!( + !fileio.exists(), + "exists() should return false when no path set" + ); } #[test] @@ -423,7 +448,10 @@ mod tests { // Test canonicalize() let canonical = fileio.canonicalize().expect("canonicalize should succeed"); - assert!(canonical.contains("phase114_canonicalize.txt"), "should contain filename"); + assert!( + canonical.contains("phase114_canonicalize.txt"), + "should contain filename" + ); assert!(canonical.starts_with("/"), "should be absolute path"); fileio.close().unwrap(); diff --git a/src/runner/dispatch.rs b/src/runner/dispatch.rs index 64deb726..f13d83bf 100644 --- a/src/runner/dispatch.rs +++ b/src/runner/dispatch.rs @@ -199,11 +199,7 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) { } // Backend selection - if std::env::var("NYASH_EMIT_MIR_TRACE") - .ok() - .as_deref() - == Some("1") - { + if std::env::var("NYASH_EMIT_MIR_TRACE").ok().as_deref() == Some("1") { eprintln!( "[dispatch] backend={} file={} path=backend-select", groups.backend.backend, filename diff --git a/src/runner/mod.rs b/src/runner/mod.rs index dd94d449..3b2ca951 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -102,11 +102,7 @@ impl NyashRunner { let groups = self.config.as_groups(); // CLI mode trace: show backend/file/args when emit-mir trace is enabled - if std::env::var("NYASH_EMIT_MIR_TRACE") - .ok() - .as_deref() - == Some("1") - { + if std::env::var("NYASH_EMIT_MIR_TRACE").ok().as_deref() == Some("1") { let backend = &groups.backend.backend; let file = groups.input.file.as_deref().unwrap_or(""); let args = std::env::args().skip(1).collect::>().join(" "); diff --git a/src/runner/modes/llvm.rs b/src/runner/modes/llvm.rs index 881e3601..39408e93 100644 --- a/src/runner/modes/llvm.rs +++ b/src/runner/modes/llvm.rs @@ -79,16 +79,19 @@ impl NyashRunner { crate::runner::modes::common_util::resolve::clone_last_merged_preludes(); if !preludes.is_empty() { crate::runtime::get_global_ring0().log.debug(&format!( - "[parse/context] merged prelude files ({}):", preludes.len() + "[parse/context] merged prelude files ({}):", + preludes.len() )); let show = std::cmp::min(16, preludes.len()); for p in preludes.iter().take(show) { - crate::runtime::get_global_ring0().log.debug(&format!(" - {}", p)); + crate::runtime::get_global_ring0() + .log + .debug(&format!(" - {}", p)); } if preludes.len() > show { - crate::runtime::get_global_ring0().log.debug(&format!( - " ... ({} more)", preludes.len() - show - )); + crate::runtime::get_global_ring0() + .log + .debug(&format!(" ... ({} more)", preludes.len() - show)); } } process::exit(1); @@ -143,9 +146,11 @@ impl NyashRunner { && crate::config::env::llvm_use_harness() { use nyash_rust::mir::join_ir::lower_skip_ws_to_joinir; - use nyash_rust::mir::join_ir_vm_bridge::convert_joinir_to_mir; + use nyash_rust::mir::join_ir_vm_bridge::bridge_joinir_to_mir; - crate::runtime::get_global_ring0().log.debug("[joinir/llvm] Attempting JoinIR path for LLVM execution"); + crate::runtime::get_global_ring0() + .log + .debug("[joinir/llvm] Attempting JoinIR path for LLVM execution"); // Try to lower Main.skip/1 to JoinIR if module.functions.contains_key("Main.skip/1") { @@ -156,7 +161,7 @@ impl NyashRunner { join_module.functions.len() )); // Convert JoinIR back to MIR' (with normalized PHI) - match convert_joinir_to_mir(&join_module) { + match bridge_joinir_to_mir(&join_module) { Ok(mir_from_joinir) => { crate::runtime::get_global_ring0().log.debug(&format!( "[joinir/llvm] ✅ Converted to MIR' ({} functions)", @@ -174,10 +179,16 @@ impl NyashRunner { for (name, func) in mir_from_joinir.functions { // Rename join_func_0 → Main.skip/1 to maintain call compatibility let target_name = if name == "join_func_0" { - crate::runtime::get_global_ring0().log.debug(&format!("[joinir/llvm] Renaming {} → Main.skip/1", name)); + crate::runtime::get_global_ring0().log.debug(&format!( + "[joinir/llvm] Renaming {} → Main.skip/1", + name + )); "Main.skip/1".to_string() } else { - crate::runtime::get_global_ring0().log.debug(&format!("[joinir/llvm] Adding JoinIR function: {}", name)); + crate::runtime::get_global_ring0().log.debug(&format!( + "[joinir/llvm] Adding JoinIR function: {}", + name + )); name }; merged.functions.insert(target_name, func); @@ -189,20 +200,31 @@ impl NyashRunner { merged } Err(e) => { - crate::runtime::get_global_ring0().log.debug(&format!("[joinir/llvm] ❌ JoinIR→MIR conversion failed: {:?}", e)); - crate::runtime::get_global_ring0().log.debug("[joinir/llvm] Falling back to original MIR"); + crate::runtime::get_global_ring0().log.debug(&format!( + "[joinir/llvm] ❌ JoinIR→MIR conversion failed: {:?}", + e + )); + crate::runtime::get_global_ring0() + .log + .debug("[joinir/llvm] Falling back to original MIR"); module } } } None => { - crate::runtime::get_global_ring0().log.debug("[joinir/llvm] ❌ JoinIR lowering returned None"); - crate::runtime::get_global_ring0().log.debug("[joinir/llvm] Falling back to original MIR"); + crate::runtime::get_global_ring0() + .log + .debug("[joinir/llvm] ❌ JoinIR lowering returned None"); + crate::runtime::get_global_ring0() + .log + .debug("[joinir/llvm] Falling back to original MIR"); module } } } else { - crate::runtime::get_global_ring0().log.debug("[joinir/llvm] Main.skip/1 not found, using original MIR"); + crate::runtime::get_global_ring0() + .log + .debug("[joinir/llvm] Main.skip/1 not found, using original MIR"); module } } else { @@ -254,7 +276,8 @@ impl NyashRunner { Err(e) => { crate::console_println!( "❌ harness output not found after emit: {} ({})", - _out_path, e + _out_path, + e ); process::exit(1); } @@ -325,7 +348,10 @@ impl NyashRunner { if !stdout_text.is_empty() { print!("{}", stdout_text); } - crate::console_println!("✅ LLVM (harness) execution completed (exit={})", code); + crate::console_println!( + "✅ LLVM (harness) execution completed (exit={})", + code + ); std::process::exit(code); } Err(e) => { @@ -358,7 +384,9 @@ impl NyashRunner { crate::console_println!("📊 Exit code: {}", exit_code); process::exit(exit_code as i32); } else { - crate::console_println!("✅ LLVM execution completed (non-integer result)!"); + crate::console_println!( + "✅ LLVM execution completed (non-integer result)!" + ); crate::console_println!("📊 Result: {}", result.to_string_box().value); } } diff --git a/src/runner/modes/vm.rs b/src/runner/modes/vm.rs index 016f5e34..947d9b0e 100644 --- a/src/runner/modes/vm.rs +++ b/src/runner/modes/vm.rs @@ -47,7 +47,9 @@ impl NyashRunner { if let Err(e) = provider_lock::set_filebox_provider(filebox_provider) { if !quiet_pipe { let ring0 = crate::runtime::ring0::get_global_ring0(); - ring0.log.warn(&format!("[warn] FileBox provider already set: {}", e)); + ring0 + .log + .warn(&format!("[warn] FileBox provider already set: {}", e)); } } @@ -94,7 +96,9 @@ impl NyashRunner { Ok(content) => content, Err(e) => { let ring0 = crate::runtime::ring0::get_global_ring0(); - ring0.log.error(&format!("❌ Error reading file {}: {}", filename, e)); + ring0 + .log + .error(&format!("❌ Error reading file {}: {}", filename, e)); process::exit(1); } }; @@ -180,11 +184,15 @@ impl NyashRunner { if let Err(e) = fs::write(&path, &code_final) { if trace { let ring0 = crate::runtime::ring0::get_global_ring0(); - ring0.log.warn(&format!("[vm/merged-hako] failed to write {}: {}", path, e)); + ring0 + .log + .warn(&format!("[vm/merged-hako] failed to write {}: {}", path, e)); } } else if trace || crate::config::env::env_bool("NYASH_VM_DUMP_MERGED_HAKO_LOG") { let ring0 = crate::runtime::ring0::get_global_ring0(); - ring0.log.debug(&format!("[vm/merged-hako] dumped merged code to {}", path)); + ring0 + .log + .debug(&format!("[vm/merged-hako] dumped merged code to {}", path)); } } @@ -226,13 +234,18 @@ impl NyashRunner { crate::runner::modes::common_util::resolve::clone_last_merged_preludes(); if !preludes.is_empty() { let ring0 = crate::runtime::ring0::get_global_ring0(); - ring0.log.debug(&format!("[parse/context] merged prelude files ({}):", preludes.len())); + ring0.log.debug(&format!( + "[parse/context] merged prelude files ({}):", + preludes.len() + )); let show = std::cmp::min(16, preludes.len()); for p in preludes.iter().take(show) { ring0.log.debug(&format!(" - {}", p)); } if preludes.len() > show { - ring0.log.debug(&format!(" ... ({} more)", preludes.len() - show)); + ring0 + .log + .debug(&format!(" ... ({} more)", preludes.len() - show)); } } process::exit(1); @@ -542,11 +555,7 @@ impl NyashRunner { // Routing logic is centralized in join_ir_vm_bridge_dispatch module try_run_joinir_vm_bridge(&module_vm, quiet_pipe); - if std::env::var("NYASH_EMIT_MIR_TRACE") - .ok() - .as_deref() - == Some("1") - { + if std::env::var("NYASH_EMIT_MIR_TRACE").ok().as_deref() == Some("1") { let ring0 = crate::runtime::ring0::get_global_ring0(); ring0.log.debug("[runner/vm] calling execute_module"); } @@ -554,13 +563,12 @@ impl NyashRunner { Ok(ret) => { use crate::box_trait::{BoolBox, IntegerBox}; - if std::env::var("NYASH_EMIT_MIR_TRACE") - .ok() - .as_deref() - == Some("1") - { + if std::env::var("NYASH_EMIT_MIR_TRACE").ok().as_deref() == Some("1") { let ring0 = crate::runtime::ring0::get_global_ring0(); - ring0.log.debug(&format!("[runner/vm] vm_result={}", ret.to_string_box().value)); + ring0.log.debug(&format!( + "[runner/vm] vm_result={}", + ret.to_string_box().value + )); } // Extract exit code from return value @@ -581,7 +589,10 @@ impl NyashRunner { if crate::config::env::env_bool("NYASH_VM_STATS") { let ring0 = crate::runtime::ring0::get_global_ring0(); let (inst, br, cmp) = vm.stats_counters(); - ring0.log.debug(&format!("[vm/stats] inst={} compare={} branch={}", inst, cmp, br)); + ring0.log.debug(&format!( + "[vm/stats] inst={} compare={} branch={}", + inst, cmp, br + )); } // Quiet mode: suppress "RC:" output for JSON-only pipelines @@ -589,13 +600,11 @@ impl NyashRunner { // Phase 105.5: Unified console output via macro crate::console_println!("RC: {}", exit_code); } - if std::env::var("NYASH_EMIT_MIR_TRACE") - .ok() - .as_deref() - == Some("1") - { + if std::env::var("NYASH_EMIT_MIR_TRACE").ok().as_deref() == Some("1") { let ring0 = crate::runtime::ring0::get_global_ring0(); - ring0.log.debug(&format!("[runner/vm] exit_code={}", exit_code)); + ring0 + .log + .debug(&format!("[runner/vm] exit_code={}", exit_code)); } // Exit with the return value as exit code @@ -603,11 +612,7 @@ impl NyashRunner { } Err(e) => { let ring0 = crate::runtime::ring0::get_global_ring0(); - if std::env::var("NYASH_EMIT_MIR_TRACE") - .ok() - .as_deref() - == Some("1") - { + if std::env::var("NYASH_EMIT_MIR_TRACE").ok().as_deref() == Some("1") { ring0.log.debug(&format!("[runner/vm] vm_error={}", e)); } ring0.log.error(&format!("❌ [rust-vm] VM error: {}", e)); diff --git a/src/runner/selfhost.rs b/src/runner/selfhost.rs index a5b08684..1e14f073 100644 --- a/src/runner/selfhost.rs +++ b/src/runner/selfhost.rs @@ -26,7 +26,9 @@ fn check_oob_strict_exit() { if crate::config::env::oob_strict_fail() && crate::runtime::observe::oob_seen() { // Phase 88: Ring0Context 経由でエラーログ出力 let ring0 = crate::runtime::ring0::get_global_ring0(); - ring0.log.error("[selfhost][oob-strict] Out-of-bounds observed → exit(1)"); + ring0 + .log + .error("[selfhost][oob-strict] Out-of-bounds observed → exit(1)"); std::process::exit(1); } } @@ -117,7 +119,9 @@ impl NyashRunner { } Err(e) => { let ring0 = crate::runtime::ring0::get_global_ring0(); - ring0.log.error(&format!("[ny-compiler] using text merge error: {}", e)); + ring0 + .log + .error(&format!("[ny-compiler] using text merge error: {}", e)); return false; } } @@ -155,7 +159,9 @@ impl NyashRunner { let tmp_dir = std::path::Path::new("tmp"); if let Err(e) = std::fs::create_dir_all(tmp_dir) { let ring0 = crate::runtime::ring0::get_global_ring0(); - ring0.log.error(&format!("[ny-compiler] mkdir tmp failed: {}", e)); + ring0 + .log + .error(&format!("[ny-compiler] mkdir tmp failed: {}", e)); return false; } @@ -206,14 +212,19 @@ impl NyashRunner { } Err(e) => { let ring0 = crate::runtime::ring0::get_global_ring0(); - ring0.log.error(&format!("[ny-compiler] pre-expand compile error: {}", e)); + ring0.log.error(&format!( + "[ny-compiler] pre-expand compile error: {}", + e + )); return false; } } } Err(e) => { let ring0 = crate::runtime::ring0::get_global_ring0(); - ring0.log.error(&format!("[ny-compiler] pre-expand parse error: {}", e)); + ring0 + .log + .error(&format!("[ny-compiler] pre-expand parse error: {}", e)); return false; } } @@ -225,13 +236,17 @@ impl NyashRunner { Ok(mut f) => { if let Err(e) = f.write_all(code_ref.as_bytes()) { let ring0 = crate::runtime::ring0::get_global_ring0(); - ring0.log.error(&format!("[ny-compiler] write tmp failed: {}", e)); + ring0 + .log + .error(&format!("[ny-compiler] write tmp failed: {}", e)); return false; } } Err(e) => { let ring0 = crate::runtime::ring0::get_global_ring0(); - ring0.log.error(&format!("[ny-compiler] open tmp failed: {}", e)); + ring0 + .log + .error(&format!("[ny-compiler] open tmp failed: {}", e)); return false; } } @@ -368,7 +383,9 @@ impl NyashRunner { } Err(e) => { let ring0 = crate::runtime::ring0::get_global_ring0(); - ring0.log.error(&format!("[ny-compiler] json parse error (child): {}", e)); + ring0 + .log + .error(&format!("[ny-compiler] json parse error (child): {}", e)); } } } @@ -391,7 +408,9 @@ impl NyashRunner { Ok(o) => o, Err(e) => { let ring0 = crate::runtime::ring0::get_global_ring0(); - ring0.log.error(&format!("[ny-compiler] python harness failed: {}", e)); + ring0 + .log + .error(&format!("[ny-compiler] python harness failed: {}", e)); return false; } }; @@ -482,7 +501,9 @@ impl NyashRunner { ) { let ring0 = crate::runtime::ring0::get_global_ring0(); - ring0.log.error(&format!("❌ PyVM MIR JSON emit error: {}", e)); + ring0 + .log + .error(&format!("❌ PyVM MIR JSON emit error: {}", e)); process::exit(1); } crate::cli_v!( diff --git a/src/runtime/box_registry.rs b/src/runtime/box_registry.rs index 5a93d30f..faf3b8b3 100644 --- a/src/runtime/box_registry.rs +++ b/src/runtime/box_registry.rs @@ -4,8 +4,8 @@ //! 旧ビルトイン経路は互換目的のAPIとして最小限に保持(テスト用途)。 use crate::box_trait::NyashBox; -use crate::runtime::plugin_config::PluginConfig; use crate::runtime::get_global_ring0; +use crate::runtime::plugin_config::PluginConfig; use std::collections::HashMap; use std::sync::{Arc, RwLock}; diff --git a/src/runtime/core_box_ids.rs b/src/runtime/core_box_ids.rs index 79afbab5..14783e3f 100644 --- a/src/runtime/core_box_ids.rs +++ b/src/runtime/core_box_ids.rs @@ -95,10 +95,10 @@ impl CoreBoxId { pub fn iter() -> impl Iterator { use CoreBoxId::*; [ - String, Integer, Bool, Array, Map, Console, - Float, Null, File, Path, Regex, Math, Time, Json, Toml, - Function, Result, Method, Missing, - ].into_iter() + String, Integer, Bool, Array, Map, Console, Float, Null, File, Path, Regex, Math, Time, + Json, Toml, Function, Result, Method, Missing, + ] + .into_iter() } /// 名前からCoreBoxIdを取得 @@ -148,7 +148,9 @@ impl CoreBoxId { match self { // Phase 106: File を CoreRequired 側に移動(selfhost/通常ランタイムでは必須) String | Integer | Bool | Array | Map | Console | File => CoreBoxCategory::CoreRequired, - Float | Null | Path | Regex | Math | Time | Json | Toml => CoreBoxCategory::CoreOptional, + Float | Null | Path | Regex | Math | Time | Json | Toml => { + CoreBoxCategory::CoreOptional + } Function | Result | Method | Missing => CoreBoxCategory::Special, } } @@ -213,9 +215,8 @@ impl CoreMethodId { pub fn box_id(&self) -> CoreBoxId { use CoreMethodId::*; match self { - StringLength | StringUpper | StringLower | - StringConcat | StringSubstring | StringIndexOf | - StringReplace | StringTrim | StringSplit => CoreBoxId::String, + StringLength | StringUpper | StringLower | StringConcat | StringSubstring + | StringIndexOf | StringReplace | StringTrim | StringSplit => CoreBoxId::String, IntegerAbs | IntegerMin | IntegerMax => CoreBoxId::Integer, @@ -282,20 +283,12 @@ impl CoreMethodId { pub fn arity(&self) -> usize { use CoreMethodId::*; match self { - StringLength | StringUpper | StringLower | StringTrim | - IntegerAbs | - BoolNot | - ArrayLength | ArrayPop | - MapKeys | - ResultIsOk | ResultGetValue => 0, + StringLength | StringUpper | StringLower | StringTrim | IntegerAbs | BoolNot + | ArrayLength | ArrayPop | MapKeys | ResultIsOk | ResultGetValue => 0, - StringConcat | StringIndexOf | StringReplace | StringSplit | - IntegerMin | IntegerMax | - BoolAnd | BoolOr | - ArrayGet | ArrayPush | - MapGet | MapHas | - ConsolePrintln | ConsoleLog | ConsoleError | - FileRead | FileWrite | FileOpen => 1, + StringConcat | StringIndexOf | StringReplace | StringSplit | IntegerMin + | IntegerMax | BoolAnd | BoolOr | ArrayGet | ArrayPush | MapGet | MapHas + | ConsolePrintln | ConsoleLog | ConsoleError | FileRead | FileWrite | FileOpen => 1, StringSubstring | MapSet => 2, } @@ -307,16 +300,15 @@ impl CoreMethodId { match self { StringLength | StringIndexOf | ArrayLength => "IntegerBox", - StringUpper | StringLower | StringConcat | StringSubstring | - StringReplace | StringTrim => "StringBox", + StringUpper | StringLower | StringConcat | StringSubstring | StringReplace + | StringTrim => "StringBox", IntegerAbs | IntegerMin | IntegerMax => "IntegerBox", BoolNot | BoolAnd | BoolOr | MapHas | ResultIsOk => "BoolBox", - ArrayPush | ArrayPop | MapSet | - ConsolePrintln | ConsoleLog | ConsoleError | - FileWrite => "Void", + ArrayPush | ArrayPop | MapSet | ConsolePrintln | ConsoleLog | ConsoleError + | FileWrite => "Void", ArrayGet | MapGet | MapKeys | StringSplit => "Unknown", @@ -330,16 +322,39 @@ impl CoreMethodId { pub fn iter() -> impl Iterator { use CoreMethodId::*; [ - StringLength, StringUpper, StringLower, StringConcat, StringSubstring, - StringIndexOf, StringReplace, StringTrim, StringSplit, - IntegerAbs, IntegerMin, IntegerMax, - BoolNot, BoolAnd, BoolOr, - ArrayLength, ArrayPush, ArrayPop, ArrayGet, - MapGet, MapSet, MapHas, MapKeys, - ConsolePrintln, ConsoleLog, ConsoleError, - FileRead, FileWrite, FileOpen, - ResultIsOk, ResultGetValue, - ].into_iter() + StringLength, + StringUpper, + StringLower, + StringConcat, + StringSubstring, + StringIndexOf, + StringReplace, + StringTrim, + StringSplit, + IntegerAbs, + IntegerMin, + IntegerMax, + BoolNot, + BoolAnd, + BoolOr, + ArrayLength, + ArrayPush, + ArrayPop, + ArrayGet, + MapGet, + MapSet, + MapHas, + MapKeys, + ConsolePrintln, + ConsoleLog, + ConsoleError, + FileRead, + FileWrite, + FileOpen, + ResultIsOk, + ResultGetValue, + ] + .into_iter() } /// Box名とメソッド名から CoreMethodId を取得 @@ -361,13 +376,11 @@ impl CoreMethodId { use CoreMethodId::*; match self { // String methods (pure - return new values, don't mutate) - StringLength | StringUpper | StringLower | - StringConcat | StringSubstring | StringIndexOf | - StringReplace | StringTrim | StringSplit => true, + StringLength | StringUpper | StringLower | StringConcat | StringSubstring + | StringIndexOf | StringReplace | StringTrim | StringSplit => true, // Integer/Bool methods (pure - mathematical operations) - IntegerAbs | IntegerMin | IntegerMax | - BoolNot | BoolAnd | BoolOr => true, + IntegerAbs | IntegerMin | IntegerMax | BoolNot | BoolAnd | BoolOr => true, // Array/Map read operations (pure - don't mutate) ArrayLength | ArrayGet => true, @@ -408,8 +421,8 @@ impl CoreMethodId { ResultIsOk => true, // Not yet whitelisted - be conservative - StringUpper | StringLower | StringConcat | - StringSubstring | StringIndexOf | StringReplace | StringTrim | StringSplit => false, + StringUpper | StringLower | StringConcat | StringSubstring | StringIndexOf + | StringReplace | StringTrim | StringSplit => false, IntegerAbs | IntegerMin | IntegerMax => false, BoolNot | BoolAnd | BoolOr => false, diff --git a/src/runtime/core_services.rs b/src/runtime/core_services.rs index c424339a..2d79ae44 100644 --- a/src/runtime/core_services.rs +++ b/src/runtime/core_services.rs @@ -3,14 +3,14 @@ //! Ring1-Core: core_required Box の Service trait 群。 //! Phase 87 CoreBoxId の core_required (6個) 全てをカバー。 -use std::sync::Arc; -use crate::runtime::CoreBoxId; use crate::box_trait::NyashBox; +use crate::runtime::CoreBoxId; +use std::sync::Arc; // Phase 96.5: Adapter実装で使用するBox型をトップレベルでimport +use crate::box_trait::{BoolBox, IntegerBox, StringBox}; use crate::boxes::array::ArrayBox; use crate::boxes::map_box::MapBox; -use crate::box_trait::{IntegerBox, StringBox, BoolBox}; /// StringBox Service trait /// @@ -155,7 +155,6 @@ impl CoreServices { // Phase 91 では trait が空なので何もしない // Phase 92 以降で各 Service の初期化を検証 } - } // ============================================================================ @@ -196,20 +195,20 @@ impl IntegerBoxAdapter { impl IntegerService for IntegerBoxAdapter { fn add(&self, a: i64, b: i64) -> i64 { - a.saturating_add(b) // オーバーフロー対策 + a.saturating_add(b) // オーバーフロー対策 } fn sub(&self, a: i64, b: i64) -> i64 { - a.saturating_sub(b) // アンダーフロー対策 + a.saturating_sub(b) // アンダーフロー対策 } fn mul(&self, a: i64, b: i64) -> i64 { - a.saturating_mul(b) // オーバーフロー対策 + a.saturating_mul(b) // オーバーフロー対策 } fn div(&self, a: i64, b: i64) -> Option { if b == 0 { - None // ゼロ除算 + None // ゼロ除算 } else { Some(a / b) } @@ -259,9 +258,9 @@ impl ArrayBoxAdapter { impl ArrayService for ArrayBoxAdapter { fn len(&self, arr: &dyn NyashBox) -> i64 { arr.as_any() - .downcast_ref::() - .map(|a| a.len() as i64) - .unwrap_or(0) + .downcast_ref::() + .map(|a| a.len() as i64) + .unwrap_or(0) } fn get(&self, arr: &dyn NyashBox, index: i64) -> Option> { @@ -271,7 +270,8 @@ impl ArrayService for ArrayBoxAdapter { } fn set(&self, arr: &dyn NyashBox, index: i64, value: Box) -> Result<(), String> { - let arr_box = arr.as_any() + let arr_box = arr + .as_any() .downcast_ref::() .ok_or("Not an ArrayBox")?; let index_box = Box::new(IntegerBox::new(index)); @@ -280,7 +280,8 @@ impl ArrayService for ArrayBoxAdapter { } fn push(&self, arr: &dyn NyashBox, value: Box) -> Result<(), String> { - let arr_box = arr.as_any() + let arr_box = arr + .as_any() .downcast_ref::() .ok_or("Not an ArrayBox")?; arr_box.push(value); @@ -302,16 +303,17 @@ impl MapBoxAdapter { impl MapService for MapBoxAdapter { fn size(&self, map: &dyn NyashBox) -> i64 { map.as_any() - .downcast_ref::() - .map(|m| { - // MapBox::size() は Box を返すため、IntegerBox に変換 - let size_box = m.size(); - size_box.as_any() - .downcast_ref::() - .map(|i| i.value) - .unwrap_or(0) - }) - .unwrap_or(0) + .downcast_ref::() + .map(|m| { + // MapBox::size() は Box を返すため、IntegerBox に変換 + let size_box = m.size(); + size_box + .as_any() + .downcast_ref::() + .map(|i| i.value) + .unwrap_or(0) + }) + .unwrap_or(0) } fn has(&self, map: &dyn NyashBox, key: &str) -> bool { @@ -321,10 +323,11 @@ impl MapService for MapBoxAdapter { }; let key_box = Box::new(StringBox::new(key)); let result = map_box.has(key_box); - result.as_any() - .downcast_ref::() - .map(|b| b.value) - .unwrap_or(false) + result + .as_any() + .downcast_ref::() + .map(|b| b.value) + .unwrap_or(false) } fn get(&self, map: &dyn NyashBox, key: &str) -> Option> { @@ -334,7 +337,8 @@ impl MapService for MapBoxAdapter { } fn set(&self, map: &dyn NyashBox, key: &str, value: Box) -> Result<(), String> { - let map_box = map.as_any() + let map_box = map + .as_any() .downcast_ref::() .ok_or("Not a MapBox")?; let key_box = Box::new(StringBox::new(key)); @@ -461,8 +465,8 @@ mod tests { #[test] fn test_array_service_basic_operations() { - use crate::boxes::array::ArrayBox; use crate::box_trait::IntegerBox; + use crate::boxes::array::ArrayBox; let arr = ArrayBox::new(); let adapter = ArrayBoxAdapter::new(); @@ -482,8 +486,8 @@ mod tests { #[test] fn test_array_service_set() { - use crate::boxes::array::ArrayBox; use crate::box_trait::IntegerBox; + use crate::boxes::array::ArrayBox; let arr = ArrayBox::new(); let adapter = ArrayBoxAdapter::new(); @@ -502,8 +506,8 @@ mod tests { #[test] fn test_map_service_basic_operations() { - use crate::boxes::map_box::MapBox; use crate::box_trait::StringBox; + use crate::boxes::map_box::MapBox; let map = MapBox::new(); let adapter = MapBoxAdapter::new(); @@ -527,15 +531,19 @@ mod tests { #[test] fn test_map_service_multiple_keys() { - use crate::boxes::map_box::MapBox; use crate::box_trait::{IntegerBox, StringBox}; + use crate::boxes::map_box::MapBox; let map = MapBox::new(); let adapter = MapBoxAdapter::new(); // set multiple keys - adapter.set(&map, "name", Box::new(StringBox::new("Alice"))).unwrap(); - adapter.set(&map, "age", Box::new(IntegerBox::new(25))).unwrap(); + adapter + .set(&map, "name", Box::new(StringBox::new("Alice"))) + .unwrap(); + adapter + .set(&map, "age", Box::new(IntegerBox::new(25))) + .unwrap(); // verify size assert_eq!(adapter.size(&map), 2); @@ -556,20 +564,20 @@ mod tests { // add assert_eq!(adapter.add(10, 20), 30); - assert_eq!(adapter.add(i64::MAX, 1), i64::MAX); // saturating + assert_eq!(adapter.add(i64::MAX, 1), i64::MAX); // saturating // sub assert_eq!(adapter.sub(20, 10), 10); - assert_eq!(adapter.sub(i64::MIN, 1), i64::MIN); // saturating + assert_eq!(adapter.sub(i64::MIN, 1), i64::MIN); // saturating // mul assert_eq!(adapter.mul(5, 6), 30); - assert_eq!(adapter.mul(i64::MAX, 2), i64::MAX); // saturating + assert_eq!(adapter.mul(i64::MAX, 2), i64::MAX); // saturating // div assert_eq!(adapter.div(20, 5), Some(4)); - assert_eq!(adapter.div(10, 3), Some(3)); // 整数除算 - assert_eq!(adapter.div(10, 0), None); // ゼロ除算 + assert_eq!(adapter.div(10, 3), Some(3)); // 整数除算 + assert_eq!(adapter.div(10, 0), None); // ゼロ除算 } #[test] diff --git a/src/runtime/deprecations.rs b/src/runtime/deprecations.rs index 4ad81f82..97aa88df 100644 --- a/src/runtime/deprecations.rs +++ b/src/runtime/deprecations.rs @@ -1,6 +1,6 @@ //! Deprecation warnings with "warn once" guards -use std::sync::OnceLock; use crate::runtime::get_global_ring0; +use std::sync::OnceLock; fn warn_once(flag: &'static OnceLock<()>, msg: &str) { if flag.get().is_none() { diff --git a/src/runtime/leak_tracker.rs b/src/runtime/leak_tracker.rs index 91f7400c..42f26706 100644 --- a/src/runtime/leak_tracker.rs +++ b/src/runtime/leak_tracker.rs @@ -1,7 +1,7 @@ +use crate::runtime::get_global_ring0; use once_cell::sync::Lazy; use std::collections::HashMap; use std::sync::Mutex; -use crate::runtime::get_global_ring0; static ENABLED: Lazy = Lazy::new(|| std::env::var("NYASH_LEAK_LOG").unwrap_or_default() == "1"); @@ -42,9 +42,10 @@ impl Drop for Reporter { if m.is_empty() { return; } - get_global_ring0() - .log - .warn(&format!("[leak] Detected {} non-finalized plugin boxes:", m.len())); + get_global_ring0().log.warn(&format!( + "[leak] Detected {} non-finalized plugin boxes:", + m.len() + )); for ((ty, id), _) in m.iter() { get_global_ring0().log.warn(&format!( " - {}(id={}) not finalized (missing fini or scope)", diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index e21068b0..07557cd1 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -7,9 +7,6 @@ pub mod core_box_ids; // Phase 87: CoreBoxId/CoreMethodId 型安全enum pub mod core_services; // Phase 91: CoreServices trait 定義 pub mod deprecations; pub mod gc; -pub mod plugin_host; // Phase 91: PluginHost skeleton -pub mod ring0; // Phase 88: Ring0Context - OS API 抽象化レイヤー -pub mod runtime_profile; // Phase 109: RuntimeProfile enum (Default/NoFs) pub mod gc_controller; pub mod gc_mode; pub mod gc_trace; @@ -19,10 +16,13 @@ pub mod nyash_runtime; pub mod observe; // Lightweight observability flags (OOB etc.) pub mod plugin_config; pub mod plugin_ffi_common; +pub mod plugin_host; // Phase 91: PluginHost skeleton pub mod plugin_loader_unified; pub mod plugin_loader_v2; pub mod provider_lock; pub mod provider_verify; +pub mod ring0; // Phase 88: Ring0Context - OS API 抽象化レイヤー +pub mod runtime_profile; // Phase 109: RuntimeProfile enum (Default/NoFs) pub mod scheduler; pub mod semantics; pub mod unified_registry; // Deprecation warnings with warn-once guards @@ -42,14 +42,14 @@ mod tests; pub use box_registry::{get_global_registry, BoxFactoryRegistry, BoxProvider}; pub use core_box_ids::{CoreBoxCategory, CoreBoxId, CoreMethodId}; // Phase 87: 型安全enum pub use plugin_config::PluginConfig; -pub use ring0::{get_global_ring0, init_global_ring0, Ring0Context}; // Phase 88: Ring0 公開 API -pub use runtime_profile::RuntimeProfile; // Phase 109: RuntimeProfile enum pub use plugin_host::CoreInitError; // Phase 92: CoreServices 初期化エラー pub use plugin_loader_unified::{ get_global_plugin_host, init_global_plugin_host, MethodHandle, PluginBoxType, PluginHost, PluginLibraryHandle, }; pub use plugin_loader_v2::{get_global_loader_v2, init_global_loader_v2, PluginLoaderV2}; +pub use ring0::{get_global_ring0, init_global_ring0, Ring0Context}; // Phase 88: Ring0 公開 API +pub use runtime_profile::RuntimeProfile; // Phase 109: RuntimeProfile enum pub mod cache_versions; pub use gc::{BarrierKind, GcHooks}; pub use nyash_runtime::{NyashRuntime, NyashRuntimeBuilder}; @@ -125,8 +125,8 @@ macro_rules! console_println { /// 3. init_global_ring0() で GLOBAL_RING0 に登録 /// 4. PluginHost 初期化時に get_global_ring0() で取得 pub fn initialize_runtime(ring0: std::sync::Arc) -> Result<(), CoreInitError> { - use crate::box_factory::UnifiedBoxRegistry; use crate::box_factory::builtin::BuiltinBoxFactory; + use crate::box_factory::UnifiedBoxRegistry; // Phase 109/112: Read RuntimeProfile from environment (this layer only) let profile = RuntimeProfile::from_env(); diff --git a/src/runtime/plugin_host.rs b/src/runtime/plugin_host.rs index fbfe7ccf..6a1c7c4f 100644 --- a/src/runtime/plugin_host.rs +++ b/src/runtime/plugin_host.rs @@ -3,12 +3,12 @@ //! Ring1-Core(Box 実装層)の構造定義。 //! Phase 92 で UnifiedBoxRegistry と接続予定。 -use std::sync::Arc; -use std::collections::HashMap; -use std::any::Any; use crate::box_factory::UnifiedBoxRegistry; use crate::runtime::CoreBoxId; use crate::runtime::RuntimeProfile; +use std::any::Any; +use std::collections::HashMap; +use std::sync::Arc; /// Phase 103: CoreServices Optional化設定 /// @@ -47,7 +47,7 @@ impl CoreServicesConfig { bool_enabled: false, array_enabled: false, map_enabled: false, - console_enabled: true, // Console is mandatory + console_enabled: true, // Console is mandatory } } } @@ -96,8 +96,16 @@ impl std::fmt::Display for CoreInitError { CoreInitError::RegistryEmpty => { write!(f, "UnifiedBoxRegistry is empty") } - CoreInitError::InvalidServiceType { box_id, expected, found } => { - write!(f, "Invalid service type for {:?}: expected {}, found {}", box_id, expected, found) + CoreInitError::InvalidServiceType { + box_id, + expected, + found, + } => { + write!( + f, + "Invalid service type for {:?}: expected {}, found {}", + box_id, expected, found + ) } } } @@ -158,7 +166,9 @@ impl PluginHost { match provider_lock::set_filebox_provider(provider) { Ok(()) => { - ring0.log.debug("[Phase 109] Ring0FsFileIo registered as default FileBox provider"); + ring0.log.debug( + "[Phase 109] Ring0FsFileIo registered as default FileBox provider", + ); } Err(_) => { // Plugin provider already registered - this is OK (plugin priority) @@ -172,7 +182,8 @@ impl PluginHost { if provider_lock::get_filebox_provider().is_none() { return Err(CoreInitError::MissingService { box_id: CoreBoxId::File, - hint: "FileBox provider not registered (required for Default profile)".to_string(), + hint: "FileBox provider not registered (required for Default profile)" + .to_string(), }); } } @@ -184,7 +195,9 @@ impl PluginHost { let provider = Arc::new(NoFsFileIo); let _ = provider_lock::set_filebox_provider(provider); - ring0.log.debug("[Phase 109] NoFsFileIo registered for NoFs profile"); + ring0 + .log + .debug("[Phase 109] NoFsFileIo registered for NoFs profile"); } } } @@ -317,12 +330,11 @@ mod tests { assert_eq!(desc.capabilities.len(), 1); } - #[test] fn test_core_services_all_fields() { // Phase 94: 実際の registry を使用してテスト - use crate::runtime::ring0::default_ring0; use crate::box_factory::builtin::BuiltinBoxFactory; + use crate::runtime::ring0::default_ring0; let ring0 = Arc::new(default_ring0()); let mut registry = UnifiedBoxRegistry::new(); @@ -341,8 +353,8 @@ mod tests { #[test] fn test_core_services_coverage() { // Phase 94: 実際の registry を使用して全フィールドが存在することを確認 - use crate::runtime::ring0::default_ring0; use crate::box_factory::builtin::BuiltinBoxFactory; + use crate::runtime::ring0::default_ring0; let ring0 = Arc::new(default_ring0()); let mut registry = UnifiedBoxRegistry::new(); @@ -410,12 +422,12 @@ mod tests { // FileBox provider が未登録の場合は、CoreBoxId::File のエラーが優先される if let Err(e) = result { let msg = format!("{}", e); - eprintln!("Error message: {}", msg); // デバッグ出力 + eprintln!("Error message: {}", msg); // デバッグ出力 assert!( - msg.contains("not found in registry") || - msg.contains("creation failed") || - msg.contains("Unknown Box type") || - msg.contains("FileBox provider not registered"), + msg.contains("not found in registry") + || msg.contains("creation failed") + || msg.contains("Unknown Box type") + || msg.contains("FileBox provider not registered"), "Error message should contain expected error patterns, got: {}", msg ); @@ -427,8 +439,8 @@ mod tests { // Phase 107/108: with_core_from_registry() は Ring0FsFileIo を自動登録するため、 // FileBox は常に利用可能になる - use crate::runtime::ring0::default_ring0; use crate::box_factory::builtin::BuiltinBoxFactory; + use crate::runtime::ring0::default_ring0; let ring0 = Arc::new(default_ring0()); let mut registry = UnifiedBoxRegistry::new(); @@ -438,14 +450,20 @@ mod tests { let result = PluginHost::with_core_from_registry(ring0, ®istry); // Phase 107/108: FileBox provider は自動登録されるため、成功するはず - assert!(result.is_ok(), "Expected success with auto-registered FileBox provider"); + assert!( + result.is_ok(), + "Expected success with auto-registered FileBox provider" + ); // Phase 108: 登録された provider は read/write 両対応 use crate::runtime::provider_lock; if let Some(provider) = provider_lock::get_filebox_provider() { let caps = provider.caps(); assert!(caps.read, "FileBox provider should support read"); - assert!(caps.write, "FileBox provider should support write (Phase 108)"); + assert!( + caps.write, + "FileBox provider should support write (Phase 108)" + ); } else { panic!("FileBox provider should be registered after with_core_from_registry"); } @@ -454,8 +472,8 @@ mod tests { #[test] fn test_with_core_from_registry_nofs_filebox_optional() { // Phase 109: NoFs profile では FileBox provider なしで OK - use crate::runtime::ring0::default_ring0; use crate::box_factory::builtin::BuiltinBoxFactory; + use crate::runtime::ring0::default_ring0; let ring0 = Arc::new(default_ring0()); let mut registry = UnifiedBoxRegistry::new(); @@ -471,7 +489,10 @@ mod tests { ); // Phase 109: FileBox は optional なので、provider なしで成功するはず - assert!(result.is_ok(), "Expected success with NoFs profile (FileBox optional)"); + assert!( + result.is_ok(), + "Expected success with NoFs profile (FileBox optional)" + ); } } @@ -500,5 +521,4 @@ mod optional_core_tests { assert!(!config.map_enabled, "map should be disabled"); assert!(config.console_enabled, "console must remain enabled"); } - } diff --git a/src/runtime/plugin_loader_v2/enabled/extern_functions.rs b/src/runtime/plugin_loader_v2/enabled/extern_functions.rs index e794d920..897ce820 100644 --- a/src/runtime/plugin_loader_v2/enabled/extern_functions.rs +++ b/src/runtime/plugin_loader_v2/enabled/extern_functions.rs @@ -12,9 +12,9 @@ use crate::boxes::map_box::MapBox; use crate::boxes::null_box::NullBox; use crate::boxes::result::NyashResultBox; use crate::boxes::token_box::TokenBox; +use crate::runtime::get_global_ring0; use crate::runtime::global_hooks; use crate::runtime::modules_registry; -use crate::runtime::get_global_ring0; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; @@ -210,9 +210,7 @@ fn handle_runtime( match method_name { "checkpoint" => { if crate::config::env::runtime_checkpoint_trace() { - get_global_ring0() - .log - .debug("[runtime.checkpoint] reached"); + get_global_ring0().log.debug("[runtime.checkpoint] reached"); } global_hooks::safepoint_and_poll(); Ok(None) diff --git a/src/runtime/plugin_loader_v2/enabled/ffi_bridge.rs b/src/runtime/plugin_loader_v2/enabled/ffi_bridge.rs index 0b604a1c..1f55b575 100644 --- a/src/runtime/plugin_loader_v2/enabled/ffi_bridge.rs +++ b/src/runtime/plugin_loader_v2/enabled/ffi_bridge.rs @@ -2,8 +2,8 @@ use crate::bid::{BidError, BidResult}; use crate::box_trait::NyashBox; -use crate::runtime::plugin_loader_v2::enabled::PluginLoaderV2; use crate::runtime::get_global_ring0; +use crate::runtime::plugin_loader_v2::enabled::PluginLoaderV2; use std::env; use std::sync::Arc; diff --git a/src/runtime/plugin_loader_v2/enabled/instance_manager.rs b/src/runtime/plugin_loader_v2/enabled/instance_manager.rs index 2462df01..66d795b4 100644 --- a/src/runtime/plugin_loader_v2/enabled/instance_manager.rs +++ b/src/runtime/plugin_loader_v2/enabled/instance_manager.rs @@ -2,11 +2,11 @@ use crate::bid::{BidError, BidResult}; use crate::box_trait::NyashBox; +use crate::runtime::get_global_ring0; use crate::runtime::plugin_loader_v2::enabled::{ types::{PluginBoxV2, PluginHandleInner}, PluginLoaderV2, }; -use crate::runtime::get_global_ring0; use std::sync::Arc; fn dbg_on() -> bool { diff --git a/src/runtime/provider_verify.rs b/src/runtime/provider_verify.rs index 2205a09c..c8eefb1f 100644 --- a/src/runtime/provider_verify.rs +++ b/src/runtime/provider_verify.rs @@ -20,8 +20,8 @@ * required_methods = ["length","concat"] */ -use std::collections::HashMap; use crate::runtime::get_global_ring0; +use std::collections::HashMap; fn parse_required_methods(spec: &str) -> HashMap> { let mut map = HashMap::new(); diff --git a/src/runtime/ring0/mod.rs b/src/runtime/ring0/mod.rs index 2a351f06..83c782c3 100644 --- a/src/runtime/ring0/mod.rs +++ b/src/runtime/ring0/mod.rs @@ -7,7 +7,7 @@ mod std_impls; mod traits; pub use errors::{IoError, TimeError}; -pub use std_impls::{NoopMem, NoFsApi, StdFs, StdIo, StdLog, StdMem, StdThread, StdTime}; +pub use std_impls::{NoFsApi, NoopMem, StdFs, StdIo, StdLog, StdMem, StdThread, StdTime}; pub use traits::{ FsApi, FsMetadata, IoApi, LogApi, LogLevel, MemApi, MemStats, ThreadApi, TimeApi, }; @@ -23,7 +23,7 @@ pub struct Ring0Context { pub io: Arc, pub time: Arc, pub log: Arc, - pub fs: Arc, // Phase 90-A + pub fs: Arc, // Phase 90-A pub thread: Arc, // Phase 90-D } @@ -123,12 +123,7 @@ mod tests { use super::*; fn unsafe_dealloc(ptr: *mut u8, size: usize) { - unsafe { - std::alloc::dealloc( - ptr, - std::alloc::Layout::from_size_align_unchecked(size, 1), - ) - } + unsafe { std::alloc::dealloc(ptr, std::alloc::Layout::from_size_align_unchecked(size, 1)) } } #[test] @@ -228,13 +223,17 @@ mod tests { #[test] fn test_nofs_api_write_all() { let api = NoFsApi; - assert!(api.write_all(std::path::Path::new("/tmp/test.txt"), b"data").is_err()); + assert!(api + .write_all(std::path::Path::new("/tmp/test.txt"), b"data") + .is_err()); } #[test] fn test_nofs_api_append_all() { let api = NoFsApi; - assert!(api.append_all(std::path::Path::new("/tmp/test.txt"), b"data").is_err()); + assert!(api + .append_all(std::path::Path::new("/tmp/test.txt"), b"data") + .is_err()); } #[test] @@ -252,6 +251,8 @@ mod tests { #[test] fn test_nofs_api_canonicalize() { let api = NoFsApi; - assert!(api.canonicalize(std::path::Path::new("/tmp/test.txt")).is_err()); + assert!(api + .canonicalize(std::path::Path::new("/tmp/test.txt")) + .is_err()); } } diff --git a/src/runtime/ring0/std_impls.rs b/src/runtime/ring0/std_impls.rs index 11a76448..9c03a040 100644 --- a/src/runtime/ring0/std_impls.rs +++ b/src/runtime/ring0/std_impls.rs @@ -125,8 +125,8 @@ pub struct StdLog; impl StdLog { fn should_log(&self, level: LogLevel) -> bool { - let min_level_str = std::env::var("NYASH_RING0_LOG_LEVEL") - .unwrap_or_else(|_| "INFO".to_string()); + let min_level_str = + std::env::var("NYASH_RING0_LOG_LEVEL").unwrap_or_else(|_| "INFO".to_string()); let min_level = match min_level_str.to_uppercase().as_str() { "DEBUG" => LogLevel::Debug, @@ -139,10 +139,13 @@ impl StdLog { // level の優先度が min_level 以上なら true matches!( (level, min_level), - (LogLevel::Error, _) | - (LogLevel::Warn, LogLevel::Debug | LogLevel::Info | LogLevel::Warn) | - (LogLevel::Info, LogLevel::Debug | LogLevel::Info) | - (LogLevel::Debug, LogLevel::Debug) + (LogLevel::Error, _) + | ( + LogLevel::Warn, + LogLevel::Debug | LogLevel::Info | LogLevel::Warn + ) + | (LogLevel::Info, LogLevel::Debug | LogLevel::Info) + | (LogLevel::Debug, LogLevel::Debug) ) } } @@ -167,9 +170,8 @@ pub struct StdFs; impl FsApi for StdFs { fn read_to_string(&self, path: &Path) -> Result { - std::fs::read_to_string(path).map_err(|e| { - IoError::ReadFailed(format!("read_to_string({}): {}", path.display(), e)) - }) + std::fs::read_to_string(path) + .map_err(|e| IoError::ReadFailed(format!("read_to_string({}): {}", path.display(), e))) } fn read(&self, path: &Path) -> Result, IoError> { @@ -187,8 +189,8 @@ impl FsApi for StdFs { use std::io::Write; let mut file = OpenOptions::new() - .create(true) // 存在しなければ作成 - .append(true) // append モードで開く + .create(true) // 存在しなければ作成 + .append(true) // append モードで開く .open(path) .map_err(|e| IoError::WriteFailed(format!("append_all({}): {}", path.display(), e)))?; @@ -201,9 +203,8 @@ impl FsApi for StdFs { } fn metadata(&self, path: &Path) -> Result { - let meta = std::fs::metadata(path).map_err(|e| { - IoError::MetadataFailed(format!("metadata({}): {}", path.display(), e)) - })?; + let meta = std::fs::metadata(path) + .map_err(|e| IoError::MetadataFailed(format!("metadata({}): {}", path.display(), e)))?; Ok(FsMetadata { is_file: meta.is_file(), is_dir: meta.is_dir(), @@ -273,12 +274,7 @@ mod stdmem_tests { use super::*; fn unsafe_dealloc(ptr: *mut u8, size: usize) { - unsafe { - std::alloc::dealloc( - ptr, - std::alloc::Layout::from_size_align_unchecked(size, 1), - ) - } + unsafe { std::alloc::dealloc(ptr, std::alloc::Layout::from_size_align_unchecked(size, 1)) } } #[test] @@ -320,6 +316,9 @@ mod stdmem_tests { fn test_noopmem_compatibility() { let mem = NoopMem; let ptr = mem.alloc(1024); - assert!(ptr.is_null(), "NoopMem should still return null for compatibility"); + assert!( + ptr.is_null(), + "NoopMem should still return null for compatibility" + ); } } diff --git a/src/runtime/scheduler.rs b/src/runtime/scheduler.rs index 02be0390..1335a479 100644 --- a/src/runtime/scheduler.rs +++ b/src/runtime/scheduler.rs @@ -98,9 +98,10 @@ impl Scheduler for SingleThreadScheduler { } } if trace { - get_global_ring0() - .log - .debug(&format!("[SCHED] poll moved={} ran={} budget={}", moved, ran, budget)); + get_global_ring0().log.debug(&format!( + "[SCHED] poll moved={} ran={} budget={}", + moved, ran, budget + )); } } } diff --git a/src/runtime/tests.rs b/src/runtime/tests.rs index ccd167f9..41729f00 100644 --- a/src/runtime/tests.rs +++ b/src/runtime/tests.rs @@ -5,7 +5,7 @@ #[cfg(test)] mod tests { use super::super::{BoxFactoryRegistry, PluginConfig}; - + use crate::box_trait::{NyashBox, StringBox}; use crate::runtime::box_registry::BoxProvider; diff --git a/src/runtime/type_meta.rs b/src/runtime/type_meta.rs index fab6a6dc..f5a2c718 100644 --- a/src/runtime/type_meta.rs +++ b/src/runtime/type_meta.rs @@ -6,10 +6,10 @@ //! which VM can call via `call_function_by_name`. //! - Versioning is sourced from `cache_versions` using label `BoxRef:{class}`. +use crate::runtime::get_global_ring0; use once_cell::sync::Lazy; use std::collections::HashMap; use std::sync::{Arc, Mutex, RwLock}; -use crate::runtime::get_global_ring0; /// Target of a method thunk #[derive(Clone, Debug)] @@ -133,9 +133,10 @@ pub fn get_or_create_type_meta(class_name: &str) -> Arc { /// Dump registry contents for diagnostics pub fn dump_registry() { let map = TYPE_META_REGISTRY.lock().unwrap(); - get_global_ring0() - .log - .debug(&format!("[REG] TypeMeta registry dump ({} types)", map.len())); + get_global_ring0().log.debug(&format!( + "[REG] TypeMeta registry dump ({} types)", + map.len() + )); for (name, meta) in map.iter() { let tbl = meta.thunks.read().ok(); let len = tbl.as_ref().map(|t| t.len()).unwrap_or(0); diff --git a/src/tests/helpers/mod.rs b/src/tests/helpers/mod.rs index 73da4da6..37d5c877 100644 --- a/src/tests/helpers/mod.rs +++ b/src/tests/helpers/mod.rs @@ -1,4 +1,4 @@ //! Phase 34-7.5: テストヘルパーモジュール -pub mod joinir_frontend; pub mod joinir_env; +pub mod joinir_frontend; diff --git a/src/tests/identical_exec.rs b/src/tests/identical_exec.rs index bfafe492..90d8247e 100644 --- a/src/tests/identical_exec.rs +++ b/src/tests/identical_exec.rs @@ -1,6 +1,6 @@ #[cfg(all(test, feature = "cranelift-jit", not(feature = "jit-direct-only")))] mod tests { - + #[allow(unused_imports)] use crate::mir::{BasicBlockId, BinaryOp, ConstValue, EffectMask, MirInstruction, MirType}; #[allow(unused_imports)] diff --git a/src/tests/identical_exec_collections.rs b/src/tests/identical_exec_collections.rs index e5e3cb3c..a3b5ec3e 100644 --- a/src/tests/identical_exec_collections.rs +++ b/src/tests/identical_exec_collections.rs @@ -1,6 +1,6 @@ #[cfg(all(test, feature = "cranelift-jit", not(feature = "jit-direct-only")))] mod tests { - + #[allow(unused_imports)] use crate::mir::{ BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirFunction, MirInstruction, diff --git a/src/tests/identical_exec_instance.rs b/src/tests/identical_exec_instance.rs index 2a945b36..bb5ea893 100644 --- a/src/tests/identical_exec_instance.rs +++ b/src/tests/identical_exec_instance.rs @@ -1,9 +1,7 @@ #[cfg(all(test, feature = "cranelift-jit", not(feature = "jit-direct-only")))] mod tests { use std::collections::HashMap; - - use crate::box_factory::RuntimeError; use crate::box_trait::NyashBox; use crate::mir::{ diff --git a/src/tests/identical_exec_string.rs b/src/tests/identical_exec_string.rs index de0a3edb..ce606ff0 100644 --- a/src/tests/identical_exec_string.rs +++ b/src/tests/identical_exec_string.rs @@ -1,6 +1,6 @@ #[cfg(all(test, feature = "cranelift-jit", not(feature = "jit-direct-only")))] mod tests { - + #[allow(unused_imports)] use crate::mir::{ BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirFunction, MirInstruction, diff --git a/src/tests/joinir_json_min.rs b/src/tests/joinir_json_min.rs index 8d173ddf..cd52b9b5 100644 --- a/src/tests/joinir_json_min.rs +++ b/src/tests/joinir_json_min.rs @@ -456,7 +456,7 @@ fn joinir_json_v0_stageb_funcscanner_min_matches_fixture() { // 実行方法: // cargo test --release joinir_stageb_structure -- --nocapture -use crate::mir::join_ir_vm_bridge::convert_joinir_to_mir; +use crate::mir::join_ir_vm_bridge::bridge_joinir_to_mir; /// Stage-B Body: JoinIR lowering + JoinIR→MIR 変換の構造テスト #[test] @@ -510,7 +510,7 @@ fn joinir_stageb_body_structure_test() { ); // Step 2: JoinIR → MIR 変換 - let mir_module = match convert_joinir_to_mir(&join_module) { + let mir_module = match bridge_joinir_to_mir(&join_module) { Ok(m) => m, Err(e) => { eprintln!("[joinir/stageb_body] JoinIR→MIR conversion failed: {:?}", e); @@ -599,7 +599,7 @@ fn joinir_stageb_funcscanner_structure_test() { ); // Step 2: JoinIR → MIR 変換 - let mir_module = match convert_joinir_to_mir(&join_module) { + let mir_module = match bridge_joinir_to_mir(&join_module) { Ok(m) => m, Err(e) => { eprintln!( diff --git a/src/tests/mir_joinir_if_select.rs b/src/tests/mir_joinir_if_select.rs index 01fa9ef7..382a3683 100644 --- a/src/tests/mir_joinir_if_select.rs +++ b/src/tests/mir_joinir_if_select.rs @@ -55,7 +55,6 @@ mod tests { use crate::mir::function::FunctionMetadata; use crate::mir::{EffectMask, MirType}; - MirFunction { signature: crate::mir::FunctionSignature { @@ -119,7 +118,6 @@ mod tests { use crate::mir::function::FunctionMetadata; use crate::mir::{EffectMask, MirType}; - MirFunction { signature: crate::mir::FunctionSignature { @@ -449,7 +447,6 @@ mod tests { use crate::mir::function::FunctionMetadata; use crate::mir::{EffectMask, MirType}; - MirFunction { signature: crate::mir::FunctionSignature { @@ -520,7 +517,6 @@ mod tests { use crate::mir::function::FunctionMetadata; use crate::mir::{EffectMask, MirType}; - MirFunction { signature: crate::mir::FunctionSignature { diff --git a/src/tests/typebox_tlv_diff.rs b/src/tests/typebox_tlv_diff.rs index 79d5d422..5ad1f0e6 100644 --- a/src/tests/typebox_tlv_diff.rs +++ b/src/tests/typebox_tlv_diff.rs @@ -3,11 +3,10 @@ mod tests { use crate::box_trait::{IntegerBox, NyashBox, StringBox}; use crate::boxes::array::ArrayBox; - + use crate::runtime::plugin_loader_unified::PluginHost; use std::env; use std::fs; - // RAII: environment variable guard (restores on drop) struct EnvGuard { diff --git a/tests/normalized_joinir_min.rs b/tests/normalized_joinir_min.rs new file mode 100644 index 00000000..797757b2 --- /dev/null +++ b/tests/normalized_joinir_min.rs @@ -0,0 +1,385 @@ +#![cfg(all(feature = "normalized_dev", debug_assertions))] + +use nyash_rust::backend::mir_interpreter::MirInterpreter; +use nyash_rust::mir::join_ir::{ + normalize_pattern1_minimal, normalize_pattern2_minimal, normalized_pattern1_to_structured, + normalized_pattern2_to_structured, BinOpKind, ConstValue, JoinContId, JoinFuncId, + JoinFunction, JoinInst, JoinIrPhase, JoinModule, MirLikeInst, +}; +use nyash_rust::mir::join_ir::normalized::fixtures::{ + build_jsonparser_skip_ws_structured_for_normalized_dev, + build_pattern2_break_fixture_structured, build_pattern2_minimal_structured, +}; +use nyash_rust::mir::join_ir_runner::run_joinir_function; +use nyash_rust::mir::join_ir_ops::JoinValue; +use nyash_rust::mir::join_ir_vm_bridge::run_joinir_via_vm; +use nyash_rust::mir::ValueId; +use once_cell::sync::Lazy; +use std::sync::Mutex; + +static NORMALIZED_ENV_LOCK: Lazy> = Lazy::new(|| Mutex::new(())); + +struct NormalizedRunGuard { + _lock: std::sync::MutexGuard<'static, ()>, + prev: Option, +} + +impl NormalizedRunGuard { + fn new(enabled: bool) -> Self { + let lock = NORMALIZED_ENV_LOCK.lock().expect("env mutex poisoned"); + let prev = std::env::var("NYASH_JOINIR_NORMALIZED_DEV_RUN").ok(); + if enabled { + std::env::set_var("NYASH_JOINIR_NORMALIZED_DEV_RUN", "1"); + } else { + std::env::remove_var("NYASH_JOINIR_NORMALIZED_DEV_RUN"); + } + Self { _lock: lock, prev } + } +} + +impl Drop for NormalizedRunGuard { + fn drop(&mut self) { + if let Some(prev) = &self.prev { + std::env::set_var("NYASH_JOINIR_NORMALIZED_DEV_RUN", prev); + } else { + std::env::remove_var("NYASH_JOINIR_NORMALIZED_DEV_RUN"); + } + } +} + +fn assert_normalized_dev_ready() { + assert!( + nyash_rust::config::env::normalized_dev_enabled(), + "Phase 33: normalized_dev must be enabled for this suite (feature + env)" + ); +} + +fn run_joinir_runner( + module: &JoinModule, + entry: JoinFuncId, + args: &[JoinValue], + normalized: bool, +) -> JoinValue { + let _guard = NormalizedRunGuard::new(normalized); + if normalized { + assert_normalized_dev_ready(); + } + let mut vm = MirInterpreter::new(); + run_joinir_function(&mut vm, module, entry, args).expect("JoinIR runner should succeed") +} + +fn run_joinir_vm_bridge( + module: &JoinModule, + entry: JoinFuncId, + args: &[JoinValue], + normalized: bool, +) -> JoinValue { + let _guard = NormalizedRunGuard::new(normalized); + if normalized { + assert_normalized_dev_ready(); + } + run_joinir_via_vm(module, entry, args).expect("JoinIR→MIR execution should succeed") +} + +fn build_structured_pattern1() -> JoinModule { + let mut module = JoinModule::new(); + let mut loop_fn = JoinFunction::new( + JoinFuncId::new(1), + "loop_step".to_string(), + vec![ValueId(10)], + ); + + loop_fn.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: ValueId(11), + value: ConstValue::Integer(0), + })); + loop_fn.body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: ValueId(12), + op: BinOpKind::Add, + lhs: ValueId(10), + rhs: ValueId(11), + })); + loop_fn.body.push(JoinInst::Jump { + cont: JoinContId(2), + args: vec![ValueId(12)], + cond: None, // 単純経路: 無条件で k_exit に渡して終了 + }); + + let mut k_exit = JoinFunction::new(JoinFuncId::new(2), "k_exit".to_string(), vec![ValueId(12)]); + k_exit.body.push(JoinInst::Ret { + value: Some(ValueId(12)), + }); + + module.entry = Some(loop_fn.id); + module.add_function(loop_fn); + module.add_function(k_exit); + module +} + +#[test] +fn normalized_pattern1_minimal_smoke() { + let structured = build_structured_pattern1(); + let normalized = normalize_pattern1_minimal(&structured); + assert_eq!(normalized.phase, JoinIrPhase::Normalized); + assert!(!normalized.env_layouts.is_empty()); + assert!(!normalized.functions.is_empty()); + + // Structured バックアップが保持されていることを確認 + let restored = normalized + .to_structured() + .expect("structured backup should exist"); + assert!(restored.is_structured()); + assert_eq!(restored.functions.len(), structured.functions.len()); +} + +#[test] +fn normalized_pattern1_roundtrip_structured_equivalent() { + let structured = build_structured_pattern1(); + let normalized = normalize_pattern1_minimal(&structured); + let reconstructed = normalized_pattern1_to_structured(&normalized); + + assert!(reconstructed.is_structured()); + assert_eq!(reconstructed.functions.len(), structured.functions.len()); + + for (fid, func) in &structured.functions { + let recon = reconstructed + .functions + .get(fid) + .expect("function missing after reconstruction"); + assert_eq!(recon.params.len(), func.params.len()); + } +} + +#[test] +fn normalized_pattern1_exec_matches_structured() { + let structured = build_structured_pattern1(); + let normalized = normalize_pattern1_minimal(&structured); + let reconstructed = normalized_pattern1_to_structured(&normalized); + + let entry = structured.entry.unwrap_or(JoinFuncId::new(1)); + let input = [JoinValue::Int(0)]; + + let result_structured = + run_joinir_via_vm(&structured, entry, &input).expect("structured run should succeed"); + let result_norm = run_joinir_via_vm(&reconstructed, entry, &input) + .expect("normalized roundtrip run should succeed"); + + assert_eq!(result_structured, result_norm); +} + +#[test] +fn normalized_pattern1_exec_matches_structured_roundtrip_backup() { + let structured = build_structured_pattern1(); + let normalized = normalize_pattern1_minimal(&structured); + let reconstructed = normalized_pattern1_to_structured(&normalized); + let restored_backup = normalized + .to_structured() + .expect("structured backup should be present"); + + let entry = structured.entry.unwrap_or(JoinFuncId::new(1)); + let input = [JoinValue::Int(0)]; + + let base = run_joinir_via_vm(&structured, entry, &input).expect("structured run"); + let recon = run_joinir_via_vm(&reconstructed, entry, &input).expect("reconstructed run"); + let restored = run_joinir_via_vm(&restored_backup, entry, &input).expect("backup run"); + + assert_eq!(base, recon); + assert_eq!(base, restored); +} + +#[test] +fn normalized_pattern2_roundtrip_structure() { + let structured = build_pattern2_minimal_structured(); + let normalized = normalize_pattern2_minimal(&structured); + assert_eq!(normalized.phase, JoinIrPhase::Normalized); + + let reconstructed = normalized_pattern2_to_structured(&normalized); + assert!(reconstructed.is_structured()); + assert_eq!(reconstructed.functions.len(), structured.functions.len()); + + for name in ["main", "loop_step", "k_exit"] { + let original_has = structured.functions.values().any(|f| f.name == name); + let reconstructed_has = reconstructed.functions.values().any(|f| f.name == name); + assert!( + original_has && reconstructed_has, + "expected function '{}' to exist in both modules", + name + ); + } +} + +#[test] +fn normalized_pattern2_exec_matches_structured() { + let structured = build_pattern2_minimal_structured(); + let normalized = normalize_pattern2_minimal(&structured); + let reconstructed = normalized_pattern2_to_structured(&normalized); + + let entry = structured.entry.unwrap_or(JoinFuncId::new(0)); + let input = [JoinValue::Int(0)]; + + let base = run_joinir_via_vm(&structured, entry, &input).expect("structured run"); + let recon = run_joinir_via_vm(&reconstructed, entry, &input).expect("normalized roundtrip run"); + + assert_eq!(base, recon); +} + +#[test] +#[should_panic(expected = "normalize_pattern2_minimal")] +fn normalized_pattern2_rejects_non_pattern2_structured() { + // Pattern1 Structured module should be rejected by Pattern2 normalizer. + let structured = build_structured_pattern1(); + let _ = normalize_pattern2_minimal(&structured); +} + +#[test] +fn normalized_pattern2_real_loop_roundtrip_structure() { + let structured = build_pattern2_break_fixture_structured(); + let normalized = normalize_pattern2_minimal(&structured); + let reconstructed = normalized_pattern2_to_structured(&normalized); + + assert!(reconstructed.is_structured()); + assert_eq!(structured.functions.len(), reconstructed.functions.len()); + assert_eq!(structured.entry, reconstructed.entry); + + let original_names: Vec<_> = structured + .functions + .values() + .map(|f| f.name.clone()) + .collect(); + for name in original_names { + let reconstructed_has = reconstructed.functions.values().any(|f| f.name == name); + assert!(reconstructed_has, "function '{}' missing after roundtrip", name); + } +} + +#[test] +fn normalized_pattern2_real_loop_exec_matches_structured() { + let structured = build_pattern2_break_fixture_structured(); + let normalized = normalize_pattern2_minimal(&structured); + let reconstructed = normalized_pattern2_to_structured(&normalized); + + let entry = structured.entry.expect("structured entry required"); + let cases = [0, 1, 3, 5]; + + for n in cases { + let input = [JoinValue::Int(n)]; + let base = + run_joinir_via_vm(&structured, entry, &input).expect("structured execution should pass"); + let recon = run_joinir_via_vm(&reconstructed, entry, &input) + .expect("normalized roundtrip execution should pass"); + + assert_eq!(base, recon, "mismatch at n={}", n); + let expected_sum = n * (n.saturating_sub(1)) / 2; + assert_eq!( + base, + JoinValue::Int(expected_sum), + "unexpected loop result at n={}", + n + ); + } +} + +#[test] +fn normalized_pattern1_runner_dev_switch_matches_structured() { + let structured = build_structured_pattern1(); + let entry = structured.entry.expect("structured entry required"); + let input = [JoinValue::Int(7)]; + + let base = run_joinir_runner(&structured, entry, &input, false); + let dev = run_joinir_runner(&structured, entry, &input, true); + + assert_eq!(base, dev); + assert_eq!(base, JoinValue::Int(7)); +} + +#[test] +fn normalized_pattern2_runner_dev_switch_matches_structured() { + let structured = build_pattern2_break_fixture_structured(); + let entry = structured.entry.expect("structured entry required"); + let cases = [0, 1, 3, 5]; + + for n in cases { + let input = [JoinValue::Int(n)]; + let base = run_joinir_runner(&structured, entry, &input, false); + let dev = run_joinir_runner(&structured, entry, &input, true); + + assert_eq!(base, dev, "runner mismatch at n={}", n); + let expected_sum = n * (n.saturating_sub(1)) / 2; + assert_eq!( + dev, + JoinValue::Int(expected_sum), + "runner result mismatch at n={}", + n + ); + } +} + +#[test] +fn normalized_pattern2_jsonparser_runner_dev_switch_matches_structured() { + let structured = build_jsonparser_skip_ws_structured_for_normalized_dev(); + let entry = structured.entry.expect("structured entry required"); + let cases = [0, 1, 2, 5]; + + for len in cases { + let input = [JoinValue::Int(len)]; + let base = run_joinir_runner(&structured, entry, &input, false); + let dev = run_joinir_runner(&structured, entry, &input, true); + + assert_eq!(base, dev, "runner mismatch at len={}", len); + assert_eq!(dev, JoinValue::Int(len), "unexpected result at len={}", len); + } +} + +#[test] +fn normalized_pattern2_vm_bridge_dev_switch_matches_structured() { + let structured = build_pattern2_break_fixture_structured(); + let entry = structured.entry.expect("structured entry required"); + let cases = [0, 1, 3, 5]; + + for n in cases { + let input = [JoinValue::Int(n)]; + let base = run_joinir_vm_bridge(&structured, entry, &input, false); + let dev = run_joinir_vm_bridge(&structured, entry, &input, true); + + assert_eq!(base, dev, "vm bridge mismatch at n={}", n); + let expected_sum = n * (n.saturating_sub(1)) / 2; + assert_eq!( + dev, + JoinValue::Int(expected_sum), + "vm bridge result mismatch at n={}", + n + ); + } +} + +#[test] +fn normalized_pattern1_vm_bridge_dev_switch_matches_structured() { + let structured = build_structured_pattern1(); + let entry = structured.entry.expect("structured entry required"); + let cases = [0, 5, 7]; + + for n in cases { + let input = [JoinValue::Int(n)]; + let base = run_joinir_vm_bridge(&structured, entry, &input, false); + let dev = run_joinir_vm_bridge(&structured, entry, &input, true); + + assert_eq!(base, dev, "vm bridge mismatch at n={}", n); + assert_eq!(dev, JoinValue::Int(n), "unexpected result at n={}", n); + } +} + +#[test] +fn normalized_pattern2_jsonparser_vm_bridge_dev_switch_matches_structured() { + let structured = build_jsonparser_skip_ws_structured_for_normalized_dev(); + let entry = structured.entry.expect("structured entry required"); + let cases = [0, 1, 2, 5]; + + for len in cases { + let input = [JoinValue::Int(len)]; + let base = run_joinir_vm_bridge(&structured, entry, &input, false); + let dev = run_joinir_vm_bridge(&structured, entry, &input, true); + + assert_eq!(base, dev, "vm bridge mismatch at len={}", len); + assert_eq!(dev, JoinValue::Int(len), "unexpected result at len={}", len); + } +}