Phase 33 NORM canon test: enforce normalized dev route for P1/P2/JP mini
This commit is contained in:
@ -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. いまコード側で意識しておきたいフォーカス
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 相当の役割を担わせている。
|
||||
|
||||
@ -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 のまま。
|
||||
|
||||
@ -68,9 +68,7 @@ impl MirInterpreter {
|
||||
})
|
||||
.unwrap_or(1_000_000);
|
||||
let mut steps: u64 = 0;
|
||||
let trace_log_path: Option<String> = std::env::var("NYASH_VM_TRACE_LOG")
|
||||
.ok()
|
||||
.map(|v| {
|
||||
let trace_log_path: Option<String> = std::env::var("NYASH_VM_TRACE_LOG").ok().map(|v| {
|
||||
let trimmed = v.trim();
|
||||
if trimmed.is_empty() || trimmed == "1" {
|
||||
"__mir__.log".to_string()
|
||||
|
||||
@ -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::<crate::boxes::console_box::ConsoleBox>() {
|
||||
if let Some(console) = bx
|
||||
.as_any()
|
||||
.downcast_ref::<crate::boxes::console_box::ConsoleBox>()
|
||||
{
|
||||
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::<crate::boxes::console_box::ConsoleBox>() {
|
||||
if let Some(console) = bx
|
||||
.as_any()
|
||||
.downcast_ref::<crate::boxes::console_box::ConsoleBox>()
|
||||
{
|
||||
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::<crate::boxes::console_box::ConsoleBox>() {
|
||||
if let Some(console) = bx
|
||||
.as_any()
|
||||
.downcast_ref::<crate::boxes::console_box::ConsoleBox>()
|
||||
{
|
||||
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::<crate::boxes::console_box::ConsoleBox>() {
|
||||
if let Some(console) = bx
|
||||
.as_any()
|
||||
.downcast_ref::<crate::boxes::console_box::ConsoleBox>()
|
||||
{
|
||||
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::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||
if let Some(_p) = bx
|
||||
.as_any()
|
||||
.downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>()
|
||||
{
|
||||
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,11 +462,9 @@ 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(|| {
|
||||
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(
|
||||
@ -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) => {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -194,9 +194,7 @@ impl StaticBoxRegistry {
|
||||
|
||||
/// 登録済み/検出済みの静的Box名一覧
|
||||
pub fn all_box_names(&self) -> impl Iterator<Item = &String> {
|
||||
self.declarations
|
||||
.keys()
|
||||
.chain(self.detected_boxes.iter())
|
||||
self.declarations.keys().chain(self.detected_boxes.iter())
|
||||
}
|
||||
|
||||
/// declarations への直接アクセス (既存コードとの互換性)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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<crate::boxes::file::provider::FileStat, String> {
|
||||
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<bool, String> {
|
||||
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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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::<BoolBox>()
|
||||
.unwrap()
|
||||
.value;
|
||||
let exists = exists_box.as_any().downcast_ref::<BoolBox>().unwrap().value;
|
||||
|
||||
assert!(exists);
|
||||
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
18
src/config/env/joinir_dev.rs
vendored
18
src/config/env/joinir_dev.rs
vendored
@ -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 を封じて、
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -344,7 +344,11 @@ impl MirBuilder {
|
||||
body: Vec<ASTNode>,
|
||||
) -> 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準備
|
||||
|
||||
@ -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
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -81,21 +81,25 @@ impl ExitMetaCollector {
|
||||
debug: bool,
|
||||
) -> Vec<LoopExitBinding> {
|
||||
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 {
|
||||
if verbose {
|
||||
eprintln!(
|
||||
"[cf_loop/exit_line] ExitMetaCollector DEBUG: Checking carrier '{}' in variable_map",
|
||||
"[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,
|
||||
};
|
||||
|
||||
if verbose {
|
||||
eprintln!(
|
||||
"[cf_loop/exit_line] ExitMetaCollector: Collected '{}' JoinIR {:?} → HOST {:?}, role={:?}",
|
||||
"[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,
|
||||
};
|
||||
|
||||
if verbose {
|
||||
eprintln!(
|
||||
"[cf_loop/exit_line] Phase 228-8: Collected ConditionOnly carrier '{}' JoinIR {:?} (not in variable_map)",
|
||||
"[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,
|
||||
};
|
||||
|
||||
if verbose {
|
||||
eprintln!(
|
||||
"[cf_loop/exit_line] Phase 247-EX: Collected FromHost carrier '{}' JoinIR {:?} (not in variable_map)",
|
||||
"[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,
|
||||
};
|
||||
|
||||
if verbose {
|
||||
eprintln!(
|
||||
"[cf_loop/exit_line] Phase 247-EX: Collected loop-local carrier '{}' JoinIR {:?} (no host slot)",
|
||||
"[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
|
||||
);
|
||||
@ -212,7 +231,6 @@ impl ExitMetaCollector {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_empty_exit_meta() {
|
||||
// This test would require full MirBuilder setup
|
||||
|
||||
@ -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<String, ValueId>,
|
||||
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(())
|
||||
|
||||
@ -79,26 +79,39 @@ impl ExitLineReconnector {
|
||||
carrier_phis: &BTreeMap<String, ValueId>,
|
||||
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
|
||||
if verbose {
|
||||
eprintln!(
|
||||
"[DEBUG-177/reconnect] ExitLineReconnector: {} exit bindings, {} carrier PHIs",
|
||||
"[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::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
if verbose {
|
||||
eprintln!(
|
||||
"[DEBUG-177/reconnect] Updated variable_map['{}'] {:?} → {:?}",
|
||||
"[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."
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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::<Vec<_>>()
|
||||
exit_bindings
|
||||
.iter()
|
||||
.map(|b| (b.carrier_name.as_str(), b.join_exit_value))
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
@ -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"));
|
||||
|
||||
@ -61,7 +61,8 @@ pub(super) fn merge_and_rewrite(
|
||||
let boundary_input_set: std::collections::HashSet<ValueId> = 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 {
|
||||
|
||||
@ -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<LoopHeaderPhiInfo, String> {
|
||||
@ -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,11 +212,15 @@ 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(|| {
|
||||
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
|
||||
@ -233,9 +243,9 @@ impl LoopHeaderPhiBuilder {
|
||||
|
||||
phi_instructions.push(phi);
|
||||
|
||||
if debug {
|
||||
if dev_debug {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Finalized carrier '{}' PHI: {:?} = phi [({:?}, {:?}), ({:?}, {:?})]",
|
||||
"[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()
|
||||
);
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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<String> = 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<String> = 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,33 +186,53 @@ 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 {
|
||||
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
|
||||
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
|
||||
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))
|
||||
.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()
|
||||
};
|
||||
|
||||
@ -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::<Vec<_>>()
|
||||
other_carriers
|
||||
.iter()
|
||||
.map(|(n, _, _, _)| n.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
@ -235,33 +263,53 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
||||
debug,
|
||||
)?
|
||||
} else {
|
||||
LoopHeaderPhiInfo::empty(remapper.get_block(
|
||||
LoopHeaderPhiInfo::empty(
|
||||
remapper
|
||||
.get_block(
|
||||
mir_module.functions.iter().next().unwrap().0,
|
||||
mir_module.functions.iter().next().unwrap().1.entry_block
|
||||
).unwrap())
|
||||
mir_module.functions.iter().next().unwrap().1.entry_block,
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
LoopHeaderPhiInfo::empty(remapper.get_block(
|
||||
LoopHeaderPhiInfo::empty(
|
||||
remapper
|
||||
.get_block(
|
||||
mir_module.functions.iter().next().unwrap().0,
|
||||
mir_module.functions.iter().next().unwrap().1.entry_block
|
||||
).unwrap())
|
||||
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::<Vec<_>>()
|
||||
loop_header_phi_info
|
||||
.carrier_phis
|
||||
.iter()
|
||||
.map(|(n, _)| n.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
// 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<String>) 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:
|
||||
|
||||
@ -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)
|
||||
///
|
||||
@ -28,9 +28,7 @@ pub(super) fn collect_values(
|
||||
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<ValueId> = BTreeSet::new();
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<Option<ValueId>, 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
|
||||
|
||||
@ -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<String, ValueId>, // 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<String, ValueId>, // 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<Vec<LoopExitBinding>, String> {
|
||||
pub(crate) fn build_loop_exit_bindings(&mut self) -> Result<Vec<LoopExitBinding>, 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,7 +349,9 @@ 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![],
|
||||
@ -362,7 +368,8 @@ mod tests {
|
||||
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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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_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};
|
||||
|
||||
@ -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<ConditionBinding, String> {
|
||||
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();
|
||||
|
||||
@ -31,10 +31,10 @@ impl TrimPatternValidator {
|
||||
ch_value: ValueId,
|
||||
whitespace_chars: &[String],
|
||||
) -> Result<ValueId, String> {
|
||||
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<ASTNode>)> {
|
||||
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,16 +130,14 @@ 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 {
|
||||
let body = vec![ASTNode::Local {
|
||||
variables: vec!["ch".to_string()],
|
||||
initial_values: vec![
|
||||
Some(Box::new(ASTNode::MethodCall {
|
||||
initial_values: vec![Some(Box::new(ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Variable {
|
||||
name: "s".to_string(),
|
||||
span: Span::unknown(),
|
||||
@ -150,11 +162,9 @@ mod tests {
|
||||
},
|
||||
],
|
||||
span: Span::unknown(),
|
||||
})),
|
||||
],
|
||||
}))],
|
||||
span: Span::unknown(),
|
||||
},
|
||||
];
|
||||
}];
|
||||
|
||||
let result = TrimPatternValidator::extract_substring_args(&body, "ch");
|
||||
assert!(result.is_some());
|
||||
@ -173,11 +183,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_extract_substring_args_wrong_var() {
|
||||
// local other_var = s.substring(0, 1)
|
||||
let body = vec![
|
||||
ASTNode::Local {
|
||||
let body = vec![ASTNode::Local {
|
||||
variables: vec!["other_var".to_string()],
|
||||
initial_values: vec![
|
||||
Some(Box::new(ASTNode::MethodCall {
|
||||
initial_values: vec![Some(Box::new(ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Variable {
|
||||
name: "s".to_string(),
|
||||
span: Span::unknown(),
|
||||
@ -194,11 +202,9 @@ mod tests {
|
||||
},
|
||||
],
|
||||
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 {
|
||||
let body = vec![ASTNode::Local {
|
||||
variables: vec!["ch".to_string()],
|
||||
initial_values: vec![
|
||||
Some(Box::new(ASTNode::MethodCall {
|
||||
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 {
|
||||
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());
|
||||
|
||||
@ -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 は削除済み。
|
||||
@ -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)
|
||||
|
||||
@ -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<Option<ValueId>, 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));
|
||||
|
||||
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -26,9 +26,8 @@ pub(in crate::mir::builder) fn extract_loop_variable_from_condition(
|
||||
condition: &ASTNode,
|
||||
) -> Result<String, String> {
|
||||
match condition {
|
||||
ASTNode::BinaryOp {
|
||||
operator, left, ..
|
||||
} if matches!(
|
||||
ASTNode::BinaryOp { operator, left, .. }
|
||||
if matches!(
|
||||
operator,
|
||||
BinaryOperator::Less
|
||||
| BinaryOperator::Greater
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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<BasicBlockId> {
|
||||
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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -59,7 +59,8 @@ impl BoundaryInjector {
|
||||
value_map: &BTreeMap<ValueId, ValueId>, // Phase 222.5-E: HashMap → BTreeMap for determinism
|
||||
phi_dst_ids: &std::collections::HashSet<ValueId>,
|
||||
debug: bool,
|
||||
) -> Result<BTreeMap<ValueId, ValueId>, String> { // Phase 222.5-E: HashMap → BTreeMap for determinism
|
||||
) -> Result<BTreeMap<ValueId, ValueId>, 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.
|
||||
@ -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,10 +136,8 @@ 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);
|
||||
@ -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() {
|
||||
|
||||
@ -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: {:?}",
|
||||
|
||||
@ -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
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -72,33 +72,23 @@ impl HintSink {
|
||||
match cfg.sink {
|
||||
HintSinkTarget::None => {}
|
||||
HintSinkTarget::Stderr => match hint {
|
||||
HintKind::ScopeEnter(id) => {
|
||||
get_global_ring0()
|
||||
HintKind::ScopeEnter(id) => get_global_ring0()
|
||||
.log
|
||||
.debug(&format!("[mir][hint] ScopeEnter({})", id))
|
||||
}
|
||||
HintKind::ScopeLeave(id) => {
|
||||
get_global_ring0()
|
||||
.debug(&format!("[mir][hint] ScopeEnter({})", id)),
|
||||
HintKind::ScopeLeave(id) => get_global_ring0()
|
||||
.log
|
||||
.debug(&format!("[mir][hint] ScopeLeave({})", id))
|
||||
}
|
||||
.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()
|
||||
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")
|
||||
}
|
||||
.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) => {
|
||||
|
||||
@ -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<ValueId> = 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
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<FunctionRoute, String> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,8 +110,7 @@ impl<'a> BoolExprLowerer<'a> {
|
||||
};
|
||||
|
||||
// Emit Compare instruction
|
||||
self.builder
|
||||
.emit_instruction(MirInstruction::Compare {
|
||||
self.builder.emit_instruction(MirInstruction::Compare {
|
||||
dst,
|
||||
op: cmp_op,
|
||||
lhs,
|
||||
@ -130,8 +129,7 @@ impl<'a> BoolExprLowerer<'a> {
|
||||
let dst = self.builder.next_value_id();
|
||||
|
||||
// Emit BinOp And instruction
|
||||
self.builder
|
||||
.emit_instruction(MirInstruction::BinOp {
|
||||
self.builder.emit_instruction(MirInstruction::BinOp {
|
||||
dst,
|
||||
op: BinaryOp::BitAnd, // Use BitAnd for logical AND
|
||||
lhs,
|
||||
@ -150,8 +148,7 @@ impl<'a> BoolExprLowerer<'a> {
|
||||
let dst = self.builder.next_value_id();
|
||||
|
||||
// Emit BinOp Or instruction
|
||||
self.builder
|
||||
.emit_instruction(MirInstruction::BinOp {
|
||||
self.builder.emit_instruction(MirInstruction::BinOp {
|
||||
dst,
|
||||
op: BinaryOp::BitOr, // Use BitOr for logical OR
|
||||
lhs,
|
||||
@ -179,8 +176,7 @@ impl<'a> BoolExprLowerer<'a> {
|
||||
let dst = self.builder.next_value_id();
|
||||
|
||||
// Emit UnaryOp Not instruction
|
||||
self.builder
|
||||
.emit_instruction(MirInstruction::UnaryOp {
|
||||
self.builder.emit_instruction(MirInstruction::UnaryOp {
|
||||
dst,
|
||||
op: crate::mir::UnaryOp::Not,
|
||||
operand: operand_val,
|
||||
|
||||
@ -210,10 +210,7 @@ impl CarrierInfo {
|
||||
variable_map: &BTreeMap<String, ValueId>, // Phase 222.5-D: HashMap → BTreeMap for determinism
|
||||
) -> Result<Self, String> {
|
||||
// Find loop variable
|
||||
let loop_var_id = variable_map
|
||||
.get(&loop_var_name)
|
||||
.copied()
|
||||
.ok_or_else(|| {
|
||||
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
|
||||
@ -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<ValueId> {
|
||||
if !self.promoted_loopbodylocals.contains(&original_name.to_string()) {
|
||||
if !self
|
||||
.promoted_loopbodylocals
|
||||
.contains(&original_name.to_string())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
@ -654,21 +657,13 @@ mod tests {
|
||||
|
||||
// Helper: Create a CarrierInfo for testing
|
||||
fn test_carrier_info(loop_var: &str, loop_id: u32, carriers: Vec<CarrierVar>) -> 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);
|
||||
|
||||
@ -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(|| {
|
||||
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"),
|
||||
|
||||
@ -107,7 +107,8 @@ impl ComplexAddendNormalizer {
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} | ASTNode::BinaryOp {
|
||||
}
|
||||
| ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Subtract,
|
||||
left,
|
||||
right,
|
||||
|
||||
@ -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<ValueId> {
|
||||
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<String> {
|
||||
let mut names: Vec<_> = self.name_to_join.keys()
|
||||
let mut names: Vec<_> = self
|
||||
.name_to_join
|
||||
.keys()
|
||||
.chain(self.captured.keys())
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
@ -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!(
|
||||
|
||||
@ -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<S: ScopeManager> {
|
||||
#[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()
|
||||
|
||||
@ -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,
|
||||
@ -278,12 +283,24 @@ fn binary_op_to_compare_op(op: &BinaryOperator) -> Option<CompareOp> {
|
||||
/// ```
|
||||
pub fn normalize_comparison(cond: &ASTNode) -> Option<NormalizedCondition> {
|
||||
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<NormalizedCondition> {
|
||||
}
|
||||
|
||||
// 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<NormalizedCondition> {
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<String> {
|
||||
pub fn extract_condition_variables(cond_ast: &ASTNode, exclude_vars: &[String]) -> Vec<String> {
|
||||
let mut all_vars = BTreeSet::new();
|
||||
collect_variables_recursive(cond_ast, &mut all_vars);
|
||||
|
||||
|
||||
@ -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() {
|
||||
} => 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() {
|
||||
|
||||
@ -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<ValueId, ExprLoweringError> {
|
||||
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<ValueId, ExprLoweringError> {
|
||||
// 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<S> for ExprLowerer<'env, 'builder, S> {
|
||||
/// Phase 244: Implement ConditionLoweringBox trait for ExprLowerer
|
||||
@ -280,13 +280,13 @@ impl<'env, 'builder, S: ScopeManager> ConditionLoweringBox<S> 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::<Pattern2ScopeManager>::is_supported_condition(&ast));
|
||||
assert!(ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(
|
||||
&ast
|
||||
));
|
||||
|
||||
// Supported: MethodCall
|
||||
let ast = ASTNode::MethodCall {
|
||||
@ -441,10 +451,14 @@ mod tests {
|
||||
arguments: vec![],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
assert!(ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast));
|
||||
assert!(ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(
|
||||
&ast
|
||||
));
|
||||
|
||||
// Unsupported: Break node
|
||||
let ast = ASTNode::Break { span: Span::unknown() };
|
||||
let ast = ASTNode::Break {
|
||||
span: Span::unknown(),
|
||||
};
|
||||
assert!(!ExprLowerer::<Pattern2ScopeManager>::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
|
||||
);
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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<S: ScopeManager>(
|
||||
scope: &S,
|
||||
@ -30,7 +30,9 @@ fn collect_vars(ast: &ASTNode, vars: &mut Vec<String>) {
|
||||
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);
|
||||
|
||||
@ -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::{
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
//! - ValueId/BTreeMap の初期化ボイラープレート統一化
|
||||
//! - Pinned/Carrier 変数の一元管理
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Entry関数構築用の統一ビルダー
|
||||
///
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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::{
|
||||
|
||||
@ -59,10 +59,7 @@ impl WhitespaceDetector {
|
||||
/// 具体的な JoinInst 生成は呼び出し側で行う。
|
||||
/// ここは判定ロジック(どの文字を空白と判定するか)を記録する。
|
||||
#[allow(dead_code)]
|
||||
pub fn build_whitespace_check_expr(
|
||||
ch_value: ValueId,
|
||||
_debug: bool,
|
||||
) -> Option<ValueId> {
|
||||
pub fn build_whitespace_check_expr(ch_value: ValueId, _debug: bool) -> Option<ValueId> {
|
||||
// NOTE: JoinInst を生成する実装は呼び出し側で行う
|
||||
// ここは判定ロジック(どの文字を空白と判定するか)を記録
|
||||
|
||||
|
||||
@ -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)
|
||||
};
|
||||
|
||||
@ -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
|
||||
///
|
||||
@ -568,9 +568,6 @@ mod tests {
|
||||
#[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)]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<ValueId> {
|
||||
self.boundary.condition_bindings.iter()
|
||||
self.boundary
|
||||
.condition_bindings
|
||||
.iter()
|
||||
.find(|b| b.name == name)
|
||||
.map(|b| b.join_value)
|
||||
}
|
||||
@ -364,17 +367,17 @@ mod tests {
|
||||
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 {
|
||||
.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();
|
||||
|
||||
@ -392,7 +395,7 @@ mod tests {
|
||||
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(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();
|
||||
|
||||
@ -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<ValueId, String> {
|
||||
fn lower_init_expr(
|
||||
&mut self,
|
||||
expr: &ASTNode,
|
||||
env: &LoopBodyLocalEnv,
|
||||
) -> Result<ValueId, String> {
|
||||
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)]
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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)
|
||||
@ -77,7 +77,6 @@ pub use with_continue::lower_loop_with_continue_to_joinir;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Pattern 1: Simple While Loop Tests
|
||||
// ========================================================================
|
||||
|
||||
@ -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,7 +301,9 @@ impl CaseALoweringShape {
|
||||
// Note: carriers is BTreeSet<String>, so each item is already a String
|
||||
let carrier_names: Vec<String> = 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 {
|
||||
@ -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");
|
||||
}
|
||||
|
||||
@ -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: {:?}",
|
||||
|
||||
@ -81,7 +81,8 @@ impl LoopUpdateAnalyzer {
|
||||
pub fn analyze_carrier_updates(
|
||||
body: &[ASTNode],
|
||||
carriers: &[CarrierVar],
|
||||
) -> BTreeMap<String, UpdateExpr> { // Phase 222.5-D: HashMap → BTreeMap for determinism
|
||||
) -> BTreeMap<String, UpdateExpr> {
|
||||
// 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")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<String> {
|
||||
fn extract_assigned_variables(
|
||||
loop_body: &[crate::ast::ASTNode],
|
||||
) -> std::collections::HashSet<String> {
|
||||
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 { .. }) {
|
||||
@ -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);
|
||||
|
||||
@ -61,11 +61,7 @@ impl LoopViewBuilder {
|
||||
///
|
||||
/// - `Some(JoinModule)`: Lowering成功
|
||||
/// - `None`: 未サポートパターン(フォールバック経路へ)
|
||||
pub fn build(
|
||||
&self,
|
||||
scope: LoopScopeShape,
|
||||
func_name: Option<&str>,
|
||||
) -> Option<JoinModule> {
|
||||
pub fn build(&self, scope: LoopScopeShape, func_name: Option<&str>) -> Option<JoinModule> {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>(
|
||||
|
||||
@ -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::<Vec<_>>()
|
||||
carrier_info
|
||||
.carriers
|
||||
.iter()
|
||||
.map(|c| &c.name)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
// ==================================================================
|
||||
@ -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::<Pattern2ScopeManager>::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(),
|
||||
@ -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)
|
||||
@ -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 {
|
||||
@ -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::<Vec<_>>()
|
||||
carrier_info
|
||||
.carriers
|
||||
.iter()
|
||||
.map(|c| c.name.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
// ==================================================================
|
||||
|
||||
@ -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();
|
||||
@ -195,7 +209,9 @@ 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 {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cmp_loop,
|
||||
op: loop_op,
|
||||
lhs: loop_lhs,
|
||||
@ -203,7 +219,9 @@ pub fn lower_if_sum_pattern(
|
||||
}));
|
||||
|
||||
// exit_cond = !cmp_loop
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::UnaryOp {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::UnaryOp {
|
||||
dst: exit_cond,
|
||||
op: UnaryOp::Not,
|
||||
operand: cmp_loop,
|
||||
@ -226,7 +244,9 @@ pub fn lower_if_sum_pattern(
|
||||
// Compare: if_var <op> 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 {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: if_cmp,
|
||||
op: if_op,
|
||||
lhs: if_lhs,
|
||||
@ -235,11 +255,15 @@ pub fn lower_if_sum_pattern(
|
||||
|
||||
// --- Then Branch ---
|
||||
// sum_then = sum + update_addend
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
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 {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: sum_then,
|
||||
op: BinOpKind::Add,
|
||||
lhs: sum_param,
|
||||
@ -247,11 +271,15 @@ pub fn lower_if_sum_pattern(
|
||||
}));
|
||||
|
||||
// count_then = count + 1
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::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 {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: count_then,
|
||||
op: BinOpKind::Add,
|
||||
lhs: count_param,
|
||||
@ -260,11 +288,15 @@ pub fn lower_if_sum_pattern(
|
||||
|
||||
// --- Else Branch ---
|
||||
// sum_else = sum + 0 (identity)
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::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 {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: sum_else,
|
||||
op: BinOpKind::Add,
|
||||
lhs: sum_param,
|
||||
@ -272,7 +304,9 @@ pub fn lower_if_sum_pattern(
|
||||
}));
|
||||
|
||||
// count_else = count + 0 (identity)
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: count_else,
|
||||
op: BinOpKind::Add,
|
||||
lhs: count_param,
|
||||
@ -280,14 +314,18 @@ pub fn lower_if_sum_pattern(
|
||||
}));
|
||||
|
||||
// --- Select ---
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::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 {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Select {
|
||||
dst: count_new,
|
||||
cond: if_cmp,
|
||||
then_val: count_then,
|
||||
@ -296,11 +334,15 @@ pub fn lower_if_sum_pattern(
|
||||
|
||||
// --- Counter Update ---
|
||||
let step_const2 = alloc_value();
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
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 {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: i_next,
|
||||
op: BinOpKind::Add,
|
||||
lhs: i_param,
|
||||
@ -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)
|
||||
@ -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<String, String> {
|
||||
/// For condition values, use `lower_value_expression()` which supports variables.
|
||||
fn extract_integer_literal(node: &ASTNode) -> Result<i64, String> {
|
||||
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 { .. }) => {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
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;
|
||||
|
||||
@ -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<VarScopeKind> {
|
||||
@ -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 {
|
||||
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 {
|
||||
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),
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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<JoinFuncId>,
|
||||
|
||||
/// 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]
|
||||
|
||||
840
src/mir/join_ir/normalized.rs
Normal file
840
src/mir/join_ir/normalized.rs
Normal file
@ -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<EnvField>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EnvField {
|
||||
pub name: String,
|
||||
pub ty: Option<crate::mir::MirType>,
|
||||
pub value_id: Option<ValueId>,
|
||||
}
|
||||
|
||||
/// 正規化済み関数 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<u32>,
|
||||
pub body: Vec<JpInst>,
|
||||
pub is_kont: bool,
|
||||
}
|
||||
|
||||
/// 正規化済み命令(最小セット)。
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum JpInst {
|
||||
Let {
|
||||
dst: ValueId,
|
||||
op: JpOp,
|
||||
args: Vec<ValueId>,
|
||||
},
|
||||
EnvLoad {
|
||||
dst: ValueId,
|
||||
env: ValueId,
|
||||
field: usize,
|
||||
},
|
||||
EnvStore {
|
||||
env: ValueId,
|
||||
field: usize,
|
||||
src: ValueId,
|
||||
},
|
||||
TailCallFn {
|
||||
target: JpFuncId,
|
||||
env: Vec<ValueId>,
|
||||
},
|
||||
TailCallKont {
|
||||
target: JpFuncId,
|
||||
env: Vec<ValueId>,
|
||||
},
|
||||
If {
|
||||
cond: ValueId,
|
||||
then_target: JpFuncId,
|
||||
else_target: JpFuncId,
|
||||
env: Vec<ValueId>,
|
||||
},
|
||||
}
|
||||
|
||||
/// 演算(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<JpFuncId, JpFunction>,
|
||||
pub entry: Option<JpFuncId>,
|
||||
pub env_layouts: Vec<EnvLayout>,
|
||||
pub phase: JoinIrPhase,
|
||||
/// Structured に戻すためのスナップショット(テスト専用)。
|
||||
pub structured_backup: Option<JoinModule>,
|
||||
}
|
||||
|
||||
impl NormalizedModule {
|
||||
pub fn to_structured(&self) -> Option<JoinModule> {
|
||||
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<ValueId> = 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<ValueId> = 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<JpFuncId> = 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<JoinModule, String> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
114
src/mir/join_ir/normalized/fixtures.rs
Normal file
114
src/mir/join_ir/normalized/fixtures.rs
Normal file
@ -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<String, UpdateExpr> = 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)
|
||||
}
|
||||
68
src/mir/join_ir/normalized/shape_guard.rs
Normal file
68
src/mir/join_ir/normalized/shape_guard.rs
Normal file
@ -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<NormalizedDevShape> {
|
||||
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)))
|
||||
}
|
||||
@ -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<JoinValue, JoinRuntimeError> {
|
||||
#[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<JoinValue, JoinRuntimeError> {
|
||||
// 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,
|
||||
|
||||
81
src/mir/join_ir_vm_bridge/bridge.rs
Normal file
81
src/mir/join_ir_vm_bridge/bridge.rs
Normal file
@ -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<MirModule, JoinIrVmBridgeError> {
|
||||
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<MirModule, JoinIrVmBridgeError> {
|
||||
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<MirModule, JoinIrVmBridgeError> {
|
||||
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<MirModule, JoinIrVmBridgeError> {
|
||||
let empty_meta: JoinFuncMetaMap = BTreeMap::new();
|
||||
bridge_joinir_to_mir_with_meta(module, &empty_meta)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user