Phase 33 NORM canon test: enforce normalized dev route for P1/P2/JP mini

This commit is contained in:
nyash-codex
2025-12-11 20:54:33 +09:00
parent 59a985b7fa
commit af6f95cd4b
170 changed files with 4423 additions and 1897 deletions

View File

@ -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-P2dev-only:
- Normalized JoinIR のミニ実装を Pattern1 に続き Pattern2 最小ケースまで拡張Structured→Normalized→Structured を比較)。
- 対応外の Structured JoinModule では normalize_pattern2_minimal が Fail-Fast するようガードを追加し、normalized_dev テストで固定。
- Phase 29-NORM-P2-APPLYdev-only:
- Phase 34 の break fixturei/acc/n の単純 break ループ)を Structured→Normalized→Structured の往復に通し、VM 実行結果が Structured 直経路と一致することを dev テストで固定。
- ガードは 3 パラメータまで緩和しつつ、DigitPos/Trim などの重いキャリアはまだ非対応のまま。
- Phase 30-NORM-P2-DEV-RUNdev-only:
- JoinIR runner に `NYASH_JOINIR_NORMALIZED_DEV_RUN=1` を追加し、Pattern1/2 ミニケースだけ Structured→Normalized→Structured を挟んで dev 実行できるようにした(`normalized_dev` + debug 限定。通常経路Structured→MIRは不変。
- Phase 31-NORM-JP-MINIdev-only:
- JsonParser 系のシンプルな P2 ループskip_whitespace ミニ fixtureを Structured→Normalized→Structured 経由でも実行し、runner dev スイッチの比較テストで Structured 直経路と一致することを固定。
- Phase 32-NORM-CANON-PREPdev-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-TESTdev-only:
- P1/P2Phase 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. いまコード側で意識しておきたいフォーカス

View File

@ -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)

View File

@ -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: TailCallEnv から 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 の骨格が共通化される**
- Pattern15 ごとに似たロジックが散らばりがちな現在と違い、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: 「TailCallEnv を MIR/VM にどう落とすか」を決める箱
- という分担にできるので、「この箱は何の質問に答えるか」が清潔に保ちやすくなる。
#### 3.3.2 初期コストと注意点
- **新しい IR モデルを 1 セット覚える必要がある**
- EnvFnKontTailCall の世界観を 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-Fastpanic/Errし、サイレントに正規化を試みない。
- `normalized_dev` feature 配下で、Structured↔Normalized↔Structured の roundtrip と VM 実行結果比較を常に追加していく運用にする。
全体として、短期的には「新しいレイヤと比較テストを抱えるコスト」が増えるけれど、中長期では Pattern2〜4 や JsonParser 系の「評価順PHIDigitPos/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 Pattern24 の軽量整理
### 3.5 Phase 27-CLEAN Pattern24 の軽量整理
- 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 相当の役割を担わせている。

View File

@ -0,0 +1,65 @@
Status: Active
Scope: Phase 26H.C ― Normalized JoinIR (Pattern1) を MIR に落とす最小ブリッジの設計と比較テスト計画dev専用
# Phase 26H.C 指示書 — Normalized→MIR ブリッジPattern1 最小)+比較テスト
## ゴール
- Normalized JoinIRNormalizedModuleから 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 のまま。

View File

@ -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()

View File

@ -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) => {

View File

@ -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);
}

View File

@ -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 への直接アクセス (既存コードとの互換性)

View File

@ -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)
}

View File

@ -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);

View File

@ -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);

View File

@ -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());
}

View File

@ -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 を封じて、

View File

@ -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;

View File

@ -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

View File

@ -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準備

View File

@ -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
),
);
}

View File

@ -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"

View File

@ -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

View File

@ -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(())

View File

@ -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."
);
}

View File

@ -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

View File

@ -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"));

View File

@ -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 {

View File

@ -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()
);
}

View File

@ -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)

View File

@ -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)

View File

@ -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:

View File

@ -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();

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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};

View File

@ -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();

View File

@ -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());

View File

@ -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)

View File

@ -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));

View File

@ -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
);
}
}

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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)

View File

@ -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() {

View File

@ -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: {:?}",

View File

@ -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
));
}
}

View File

@ -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()
}

View File

@ -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) => {

View File

@ -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

View File

@ -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,
}
}
}

View File

@ -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,
}
}

View File

@ -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,
}
}
}

View File

@ -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),
}
}

View File

@ -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,
}
}

View File

@ -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,
}
}
}

View File

@ -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,

View File

@ -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);

View File

@ -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"),

View File

@ -107,7 +107,8 @@ impl ComplexAddendNormalizer {
left,
right,
..
} | ASTNode::BinaryOp {
}
| ASTNode::BinaryOp {
operator: BinaryOperator::Subtract,
left,
right,

View File

@ -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();

View File

@ -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!(

View File

@ -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()

View File

@ -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));
}
}

View File

@ -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);

View File

@ -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() {

View File

@ -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
);

View File

@ -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 {

View File

@ -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);

View File

@ -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::{

View File

@ -4,8 +4,8 @@
//! - ValueId/BTreeMap の初期化ボイラープレート統一化
//! - Pinned/Carrier 変数の一元管理
use std::collections::BTreeMap;
use crate::mir::ValueId;
use std::collections::BTreeMap;
/// Entry関数構築用の統一ビルダー
///

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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::{

View File

@ -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 を生成する実装は呼び出し側で行う
// ここは判定ロジック(どの文字を空白と判定するか)を記録

View File

@ -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)
};

View File

@ -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)]);
}
}

View File

@ -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();

View File

@ -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)]

View File

@ -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);
}

View File

@ -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
// ========================================================================

View File

@ -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");
}

View File

@ -147,7 +147,10 @@ impl LoopToJoinLowerer {
}
// Phase 33-23: Validator箱に検証を委譲
if !self.validator.is_supported_case_a(func, &region, &exit_edges, &scope) {
if !self
.validator
.is_supported_case_a(func, &region, &exit_edges, &scope)
{
if self.debug {
eprintln!(
"[LoopToJoinLowerer] rejected by validator: {:?}",

View File

@ -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")
);
}
}
}

View File

@ -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);

View File

@ -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)
}

View File

@ -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

View File

@ -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>(

View File

@ -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<_>>()
);
// ==================================================================

View File

@ -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 { .. }) => {

View File

@ -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

View File

@ -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;

View File

@ -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),

View File

@ -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

View File

@ -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 直後の構造化 JoinIRPattern15 / 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]

View 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 のシンプル whilebreak/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());
}
}

View 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)
}

View 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)))
}

View File

@ -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,

View 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