diff --git a/docs/development/current/main/joinir-architecture-overview.md b/docs/development/current/main/joinir-architecture-overview.md index fc65afd9..ee8ae6f9 100644 --- a/docs/development/current/main/joinir-architecture-overview.md +++ b/docs/development/current/main/joinir-architecture-overview.md @@ -70,6 +70,12 @@ JoinIR ラインで守るべきルールを先に書いておくよ: - LoopBuilder は物理削除済み。JoinIR を OFF にする経路やフォールバックは存在しない。 - `NYASH_JOINIR_CORE` は deprecated(0 を指定しても警告して無視)。JoinIR の OFF トグルは提供しない。 +11. **Parser の直後に JoinIR 正規化ゲートを置く** + - `.hako → AST` のあと、必ず JoinIR Frontend(Pattern 検出+ lowering)を通してから MIR/LLVM に進むのを基本線とする。 + - dev/ci/strict 系プロファイルでは、この JoinIR 正規化がビルドゲートになる(ここで `[joinir/freeze]` が出たらビルド失敗)。 + - relaxed / 実験用プロファイルでは、一時的に JoinIR 正規化をスキップすることはあっても、最終的な「言語としての安全域」は + JoinIR Frontend が受理できる構造で定義する。 + --- ## 1.9 ValueId Space Management (Phase 201) @@ -118,6 +124,33 @@ Local Region (1000+): | Pattern 2 lowerer | Local (1000+) | `alloc_local()` | 中間値(Const, BinOp, etc.) | | Pattern 3 lowerer | Local (1000+) | `alloc_local()` | 中間値(PHI, Select, etc.) | | Pattern 4 lowerer | Local (1000+) | `alloc_local()` | 中間値(Select, BinOp, etc.) | + +--- + +## 7. 将来の拡張(例外 / async / try-catch) + +現状の JoinIR は「同期・正常系」のループ/if/更新を扱う芯としてはほぼ完成しており、 +LoopBuilder 完全削除後も P1–P5 + 各種 UpdateKind で JsonParser/selfhost の代表ケースを安全に lowering できている。 + +一方で、言語としては将来的に `throw/try/catch` や async/await 相当の表現も必要になる。その扱いについては、 +以下の方針をここに固定しておく(まだ実装フェーズには入っていない): + +1. **ErrorCarrier(Result 風キャリア)の導入(将来フェーズ)** + - 関数/ループのキャリアに `err_flag` / `err_value` を追加し、「エラーが発生したら err キャリアを立てて継続を上に抜けていく」 + という形で throw 相当を表現する。 + - catch 側は「ある関数境界で err キャリアを inspect して処理する」箱として実装し、構文の try/catch はこの箱への sugar として扱う。 + - これにより core JoinIR(ループ骨格・PHI・ExitLine)は変更せず、ErrorCarrier を 1 本足すだけで例外伝播を表現できる。 + +2. **async/await は state machine 箱として後置する** + - async 関数は `.hako AST → state machine JoinIR` へ変換する専用の箱で表現し、その state machine 自体は + 既存の JoinIR(ループ+if+キャリア)で実装する。 + - これにより、JoinIR のコア設計を同期用に保ったまま、外側に async 用の変換レイヤーを追加する形にする。 + +3. **導入タイミング** + - まずは JsonParser/selfhost の同期ループ/if が JoinIR Pattern 群でほぼ全部通る状態を優先して作る。 + - ErrorCarrier や try/catch/async の実装は、その後の「第2章」として Phase 23x 以降に扱う(現在は設計メモのみ)。 + +この方針により、「例外や async のために JoinIR の芯を崩さず、外側に箱を足す」拡張戦略を維持する。 | LoopHeaderPhiBuilder | PHI Reserved (0-99) | `reserve_phi()` | PHI dst ID 保護(上書き防止) | ### 1.9.4 設計原則 diff --git a/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs b/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs index 0bc325bb..a3eaf053 100644 --- a/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs +++ b/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs @@ -282,9 +282,27 @@ pub(super) fn merge_and_rewrite( // In the header block, carriers are defined by PHIs, not Copies. // JoinIR function parameters get copied to local variables, but after // inlining with header PHIs, those Copies would overwrite the PHI results. - if let MirInstruction::Copy { dst, src: _ } = inst { + if let MirInstruction::Copy { dst, src } = inst { // Check if this Copy's dst (after remapping) matches any header PHI dst let remapped_dst = remapper.get_value(*dst).unwrap_or(*dst); + let remapped_src = remapper.get_value(*src).unwrap_or(*src); + + eprintln!( + "[DEBUG-220C] Copy instruction: {:?} = {:?} → remapped: {:?} = {:?}", + dst, src, remapped_dst, remapped_src + ); + + // Phase 220-C: Skip self-copies (src == dst after remapping) + // These occur when condition variables are remapped to HOST values, + // and the JoinIR Copy becomes a pointless self-copy. + if remapped_src == remapped_dst { + eprintln!( + "[cf_loop/joinir] Phase 220-C: ✅ Skipping self-copy {:?} = {:?} (remapped to {:?})", + dst, src, remapped_dst + ); + continue; // Skip - pointless self-copy + } + let is_header_phi_dst = loop_header_phi_info.carrier_phis .values() .any(|entry| entry.phi_dst == remapped_dst); @@ -403,23 +421,41 @@ pub(super) fn merge_and_rewrite( } } else { // Insert Copy instructions for parameter binding + eprintln!("[DEBUG-220C-PARAM] Processing {} param bindings", args.len()); for (i, arg_val_remapped) in args.iter().enumerate() { if i < target_params.len() { let param_val_original = target_params[i]; + eprintln!( + "[DEBUG-220C-PARAM] Param[{}]: original={:?}, remapped lookup...", + i, param_val_original + ); if let Some(param_val_remapped) = remapper.get_value(param_val_original) { + eprintln!( + "[DEBUG-220C-PARAM] Param[{}]: {:?} → {:?}, arg={:?}", + i, param_val_original, param_val_remapped, arg_val_remapped + ); + + // Phase 220-C: Skip self-copies (arg == param after remapping) + // This occurs when condition variables are remapped to HOST values + if param_val_remapped == *arg_val_remapped { + eprintln!( + "[cf_loop/joinir] Phase 220-C: ✅ Skip self-copy param binding {:?} = {:?}", + param_val_remapped, arg_val_remapped + ); + continue; // Skip self-copy + } + new_block.instructions.push(MirInstruction::Copy { dst: param_val_remapped, src: *arg_val_remapped, }); - if debug { - eprintln!( - "[cf_loop/joinir] Param binding: arg {:?} → param {:?}", - arg_val_remapped, param_val_remapped - ); - } + eprintln!( + "[DEBUG-220C-PARAM] Added Copy: {:?} = {:?}", + param_val_remapped, arg_val_remapped + ); } } } diff --git a/src/mir/builder/control_flow/joinir/merge/mod.rs b/src/mir/builder/control_flow/joinir/merge/mod.rs index 8353c10f..9ec28b02 100644 --- a/src/mir/builder/control_flow/joinir/merge/mod.rs +++ b/src/mir/builder/control_flow/joinir/merge/mod.rs @@ -87,19 +87,12 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( let (mut used_values, value_to_func_name, function_params) = value_collector::collect_values(mir_module, &remapper, debug)?; - // Phase 171-fix: Add condition_bindings' join_values to used_values for remapping - if let Some(boundary) = boundary { - for binding in &boundary.condition_bindings { - if debug { - eprintln!( - "[cf_loop/joinir] Phase 171-fix: Adding condition binding '{}' JoinIR {:?} to used_values", - binding.name, binding.join_value - ); - } - used_values.insert(binding.join_value); - } + // Phase 220-C: Condition bindings should NOT be added to used_values + // They will be remapped directly to HOST values in remap_values() + // (Removed Phase 171-fix logic) - // Phase 172-3: Add exit_bindings' join_exit_values to used_values for remapping + // Phase 172-3: Add exit_bindings' join_exit_values to used_values for remapping + if let Some(boundary) = boundary { for binding in &boundary.exit_bindings { if debug { eprintln!( @@ -197,7 +190,8 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( } // Phase 3: Remap ValueIds (with reserved PHI dsts protection) - remap_values(builder, &used_values, &mut remapper, &reserved_phi_dsts, debug)?; + // Phase 220-C: Pre-populate condition_bindings BEFORE remap_values + remap_values(builder, &used_values, &mut remapper, &reserved_phi_dsts, boundary, debug)?; // Phase 177-3 DEBUG: Verify remapper state after Phase 3 eprintln!("[DEBUG-177] === Remapper state after Phase 3 ==="); @@ -618,11 +612,15 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( /// Phase 201-A: Accept reserved ValueIds that must not be reused. /// These are PHI dst ValueIds that will be created by LoopHeaderPhiBuilder. /// We must skip these IDs to prevent carrier value corruption. +/// +/// Phase 220-C: Pre-populate condition_bindings to use HOST ValueIds directly. +/// Condition variables should NOT get new allocations - they use HOST values. fn remap_values( builder: &mut crate::mir::builder::MirBuilder, used_values: &std::collections::BTreeSet, remapper: &mut crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper, reserved_ids: &std::collections::HashSet, + boundary: Option<&JoinInlineBoundary>, debug: bool, ) -> Result<(), String> { if debug { @@ -630,7 +628,32 @@ fn remap_values( used_values.len(), reserved_ids.len()); } + // Phase 220-C: Pre-populate condition_bindings BEFORE normal remapping + // Condition variables use HOST ValueIds directly (no new allocation) + if let Some(boundary) = boundary { + for binding in &boundary.condition_bindings { + remapper.set_value(binding.join_value, binding.host_value); + if debug { + eprintln!( + "[cf_loop/joinir] Phase 220-C: Condition '{}': JoinIR {:?} → HOST {:?} (no allocation)", + binding.name, binding.join_value, binding.host_value + ); + } + } + } + for old_value in used_values { + // Phase 220-C: Skip if already mapped (e.g., condition_bindings) + if remapper.get_value(*old_value).is_some() { + if debug { + eprintln!( + "[cf_loop/joinir] Phase 220-C: Skipping {:?} (already mapped)", + old_value + ); + } + continue; + } + // Phase 201-A: Allocate new ValueId, skipping reserved PHI dsts let new_value = loop { let candidate = builder.next_value_id();