diff --git a/src/mir/builder/control_flow/joinir/legacy/README.md b/src/mir/builder/control_flow/joinir/legacy/README.md new file mode 100644 index 00000000..f00e4c20 --- /dev/null +++ b/src/mir/builder/control_flow/joinir/legacy/README.md @@ -0,0 +1,36 @@ +# Legacy JoinIR Routing + +## 残す理由 + +- **既存コードパスとの互換性維持**: Pattern 1-4 の段階的移行中は legacy routing が必要 +- **段階的移行のための過渡期対応**: Normalized shadow が全ケースをカバーするまでの橋渡し +- **回帰テスト基盤**: 既存の動作を保持しながら新しい routing を並行開発 + +## 撤去条件 + +以下の条件がすべて満たされたときに legacy routing を削除する: + +1. **Normalized shadow が全ケースをカバー**: Phase 131+ の normalized loop(true) パターンが完全に動作 +2. **Pattern 1-4 が完全に安定**: 既存のループパターンすべてが新 routing で動作 +3. **回帰テストが完全にカバー**: 既存テストがすべて新 routing でパス +4. **依存箇所がゼロ**: routing.rs の fallback path が完全に削除済み + +## 依存箇所 + +現在 legacy routing を使用している箇所: + +- `routing.rs` の fallback path: Pattern 1-4 が新 routing で処理できない場合の退避経路 +- テストケース: 一部のテストが legacy routing を前提としている可能性 + +## 移行ステップ(将来) + +1. **Phase 132+**: Pattern 1-4 の新 routing 完成 +2. **Phase 135+**: 回帰テスト全通過確認 +3. **Phase 140+**: routing.rs の fallback path 削除 +4. **Phase 145+**: legacy/ ディレクトリ削除 + +## ファイル構成 + +- `routing_legacy_binding.rs`: Legacy binding system(既存コード) +- `mod.rs`: Module export(このファイル) +- `README.md`: このファイル(削除条件・移行計画) diff --git a/src/mir/builder/control_flow/joinir/legacy/mod.rs b/src/mir/builder/control_flow/joinir/legacy/mod.rs new file mode 100644 index 00000000..6bfc3c46 --- /dev/null +++ b/src/mir/builder/control_flow/joinir/legacy/mod.rs @@ -0,0 +1,10 @@ +//! Phase 132-R0 Task 4: Legacy JoinIR Routing +//! +//! This module contains legacy routing code that is maintained for backward +//! compatibility during the transition to normalized shadow routing. +//! +//! # Removal Conditions +//! +//! See README.md for detailed removal conditions and migration plan. + +pub(super) mod routing_legacy_binding; diff --git a/src/mir/builder/control_flow/joinir/routing_legacy_binding.rs b/src/mir/builder/control_flow/joinir/legacy/routing_legacy_binding.rs similarity index 93% rename from src/mir/builder/control_flow/joinir/routing_legacy_binding.rs rename to src/mir/builder/control_flow/joinir/legacy/routing_legacy_binding.rs index 3132f1aa..c1f2a6f1 100644 --- a/src/mir/builder/control_flow/joinir/routing_legacy_binding.rs +++ b/src/mir/builder/control_flow/joinir/legacy/routing_legacy_binding.rs @@ -7,7 +7,8 @@ //! Phase 194+ uses the pattern-based router instead. This legacy path is //! kept for backward compatibility with existing whitelist entries. -use super::trace; +// Phase 132-R0 Task 4: Fixed imports for legacy/ subdirectory +use super::super::trace; use crate::ast::ASTNode; use crate::mir::builder::MirBuilder; use crate::mir::ValueId; @@ -30,7 +31,7 @@ impl MirBuilder { func_name: &str, debug: bool, ) -> Result, String> { - use super::super::super::loop_frontend_binding::LoopFrontendBinding; + use crate::mir::builder::loop_frontend_binding::LoopFrontendBinding; use crate::mir::join_ir::frontend::{AstToJoinIrLowerer, JoinFuncMetaMap}; use crate::mir::join_ir_vm_bridge::bridge_joinir_to_mir_with_meta; use crate::mir::types::ConstValue; @@ -188,8 +189,10 @@ impl MirBuilder { } // Step 5: Merge MIR blocks into current_function - // Phase 188-Impl-3: Pass None for boundary (whitelist path without boundary tracking) - let _ = self.merge_joinir_mir_blocks(&mir_module, None, debug)?; + // Phase 132 P1: Always pass boundary with continuation contract (no by-name guessing in merge) + use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary; + let boundary = JoinInlineBoundary::new_inputs_only(vec![], vec![]); + let _ = self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?; // Return void let void_val = self.next_value_id(); diff --git a/src/mir/builder/control_flow/joinir/merge/README.md b/src/mir/builder/control_flow/joinir/merge/README.md index e7d6fad0..6a39cba7 100644 --- a/src/mir/builder/control_flow/joinir/merge/README.md +++ b/src/mir/builder/control_flow/joinir/merge/README.md @@ -18,3 +18,59 @@ Fail-Fast の基本: 1. 新しい JoinInst → MIR 変換を追加する場合、`instruction_rewriter` に閉じて追加し、PHI/ExitLine 契約が壊れないか確認。 2. PHI の入力ブロックを触るときは `phi_block_remapper` 経由に寄せて二重 remap を防ぐ。 3. 増やした box/契約はここ(README)に一言追記して入口を明示。 + +--- + +## JoinIR Merge Contracts (SSOT) + +### Phase 132-R0: Continuation Contract + +#### Continuation Functions + +- **Source**: continuation funcs は `JoinInlineBoundary.continuation_func_ids` から来る +- **Responsibility**: router/lowerer が責務(merge は推測しない) +- **Forbidden**: merge は by-name/by-id で continuation 判定しない + +#### Skip Conditions + +Merge は構造条件のみで continuation のスキップを決定する: + +- **Structural only**: 構造条件のみで決定 + - 1 block + - instruction なし + - Return のみ +- **Skippable continuation**: 上記条件を満たす continuation +- **Non-skippable continuation**: TailCall(post_k) など他関数への呼び出しを含む + +**判定関数**: `is_skippable_continuation(func: &MirFunction) -> bool` + +#### Input Contracts + +- `JoinInlineBoundary.continuation_func_ids`: Set +- Merge は受け取った ID のみを continuation として扱う +- SSOT: `JoinInlineBoundary::default_continuations()` を使用 + +#### Prohibited Behaviors + +- ❌ By-name classification (例: "join_func_2" という名前で判定) +- ❌ By-id heuristics (例: id == 2 だから continuation) +- ❌ Implicit inference (continuation 候補を merge が推測) + +#### Verification + +Continuation 契約のテストは `tests/continuation_contract.rs` に配置する。 + +#### Design Rationale + +**なぜ merge は推測してはいけないのか?** + +1. **責任分離**: Router/lowerer が JoinIR 構造を知っている。Merge は受け取った指示に従う。 +2. **拡張性**: 将来的に複数の continuation パターン(k_exit, k_continue など)が増える可能性がある。 +3. **デバッグ性**: Continuation 判定ロジックが router/lowerer に集約されているため、トラブル時の追跡が容易。 +4. **Fail-Fast**: Merge が勝手に推測して間違った挙動をするより、明示的な契約違反でエラーを出す方が安全。 + +**構造判定は許可される理由**: + +- 1-block + empty instructions + Return は「何もしない関数」の普遍的な構造的特徴。 +- この判定に名前や ID は不要(構造のみで決定可能)。 +- スキップは最適化であり、スキップしなくても正しさは保たれる。 diff --git a/src/mir/builder/control_flow/joinir/merge/tail_call_lowering_policy.rs b/src/mir/builder/control_flow/joinir/merge/tail_call_lowering_policy.rs index 154c78d4..e642fe7c 100644 --- a/src/mir/builder/control_flow/joinir/merge/tail_call_lowering_policy.rs +++ b/src/mir/builder/control_flow/joinir/merge/tail_call_lowering_policy.rs @@ -2,13 +2,13 @@ //! //! Phase 131 Task 2: Extracted from instruction_rewriter.rs //! -//! This box encapsulates the policy for lowering tail calls to k_exit (continuation functions). -//! In JoinIR, k_exit represents exit points from control flow fragments. These must be -//! normalized to Jump instructions targeting the shared exit block. +//! This box encapsulates the policy for lowering tail calls to *skippable continuation functions*. +//! Continuation functions are declared by contract (JoinInlineBoundary), and only those that are +//! structurally skippable (pure exit stubs) are lowered to Jump(exit_block_id). //! //! # Responsibility //! -//! - Detect tail calls to k_exit (continuation function) +//! - Detect tail calls to skippable continuation functions //! - Convert them to Jump(exit_block_id) instructions //! - This is "exit edge normalization" - ensuring continuation exits become proper jumps //! @@ -18,8 +18,8 @@ //! - Remapping of ValueIds (handled by JoinIrIdRemapper) //! - Instruction rewriting of non-k_exit instructions (handled by instruction_rewriter) -use crate::mir::join_ir_vm_bridge::join_func_name; use crate::mir::{BasicBlockId, MirInstruction, ValueId}; +use std::collections::BTreeSet; /// Policy box for tail call lowering (k_exit special case) /// @@ -29,7 +29,7 @@ use crate::mir::{BasicBlockId, MirInstruction, ValueId}; /// - **Output**: `Option` - None if not k_exit, Some(decision) if k_exit /// - **Invariant**: Stateless (no mutable state) pub struct TailCallLoweringPolicyBox { - k_exit_func_name: String, + skippable_continuation_func_names: BTreeSet, } /// Decision for how to lower a tail call @@ -46,9 +46,9 @@ pub enum LoweringDecision { impl TailCallLoweringPolicyBox { /// Create new policy box - pub fn new() -> Self { + pub fn new(skippable_continuation_func_names: BTreeSet) -> Self { Self { - k_exit_func_name: join_func_name(crate::mir::join_ir::JoinFuncId::new(2)), + skippable_continuation_func_names, } } @@ -68,13 +68,13 @@ impl TailCallLoweringPolicyBox { callee_name: &str, args: &[ValueId], ) -> Option { - if callee_name == self.k_exit_func_name { - // This is a k_exit tail call - must be normalized to exit jump + if self.skippable_continuation_func_names.contains(callee_name) { + // This is a skippable continuation tail call - normalize to exit jump Some(LoweringDecision::NormalizeToExitJump { args: args.to_vec(), }) } else { - // Not a k_exit call - caller will handle as normal tail call (Jump to entry block) + // Not a skippable continuation - caller will handle as normal tail call (Jump to entry block) None } } @@ -113,7 +113,7 @@ impl TailCallLoweringPolicyBox { /// /// # Contract /// - /// In strict mode, k_exit tail calls MUST become Jump(exit_block_id). + /// In strict mode, skippable continuation tail calls MUST become Jump(exit_block_id). /// Any other terminator is a contract violation. pub fn verify_exit_jump( &self, @@ -125,18 +125,18 @@ impl TailCallLoweringPolicyBox { MirInstruction::Jump { target } => Err(crate::mir::join_ir::lowering::error_tags::freeze_with_hint( "phase131/k_exit/wrong_jump_target", &format!( - "k_exit tail call lowered to Jump {:?}, expected exit_block_id {:?}", + "skippable continuation tail call lowered to Jump {:?}, expected exit_block_id {:?}", target, exit_block_id ), - "k_exit continuation blocks are not merged; ensure k_exit calls become an exit jump to the shared exit block", + "skippable continuation blocks are not merged; ensure calls to skipped continuations become an exit jump to the shared exit block", )), other => Err(crate::mir::join_ir::lowering::error_tags::freeze_with_hint( "phase131/k_exit/not_jump", &format!( - "k_exit tail call resulted in unexpected terminator: {:?}", + "skippable continuation tail call resulted in unexpected terminator: {:?}", other ), - "k_exit must be lowered to Jump(exit_block_id)", + "skippable continuations must be lowered to Jump(exit_block_id)", )), } } @@ -144,18 +144,20 @@ impl TailCallLoweringPolicyBox { impl Default for TailCallLoweringPolicyBox { fn default() -> Self { - Self::new() + Self::new(BTreeSet::new()) } } #[cfg(test)] mod tests { use super::*; + use crate::mir::join_ir_vm_bridge::join_func_name; + use crate::mir::join_ir::JoinFuncId; #[test] - fn test_classify_k_exit_call() { - let policy = TailCallLoweringPolicyBox::new(); - let k_exit_name = join_func_name(crate::mir::join_ir::JoinFuncId::new(2)); + fn test_classify_skippable_continuation_call() { + let k_exit_name = join_func_name(JoinFuncId::new(2)); + let policy = TailCallLoweringPolicyBox::new(BTreeSet::from([k_exit_name.clone()])); let args = vec![ValueId(1), ValueId(2)]; let decision = policy.classify_tail_call(&k_exit_name, &args); @@ -167,7 +169,7 @@ mod tests { #[test] fn test_classify_normal_call() { - let policy = TailCallLoweringPolicyBox::new(); + let policy = TailCallLoweringPolicyBox::new(BTreeSet::new()); let args = vec![ValueId(1)]; let decision = policy.classify_tail_call("some_other_function", &args); @@ -176,7 +178,7 @@ mod tests { #[test] fn test_rewrite_to_exit_jump() { - let policy = TailCallLoweringPolicyBox::new(); + let policy = TailCallLoweringPolicyBox::new(BTreeSet::new()); let exit_block = BasicBlockId(42); let jump = policy.rewrite_to_exit_jump(exit_block); @@ -188,7 +190,7 @@ mod tests { #[test] fn test_verify_exit_jump_success() { - let policy = TailCallLoweringPolicyBox::new(); + let policy = TailCallLoweringPolicyBox::new(BTreeSet::new()); let exit_block = BasicBlockId(42); let jump = MirInstruction::Jump { target: exit_block, @@ -200,7 +202,7 @@ mod tests { #[test] fn test_verify_exit_jump_wrong_target() { - let policy = TailCallLoweringPolicyBox::new(); + let policy = TailCallLoweringPolicyBox::new(BTreeSet::new()); let exit_block = BasicBlockId(42); let wrong_jump = MirInstruction::Jump { target: BasicBlockId(99), diff --git a/src/mir/builder/control_flow/joinir/merge/tests/continuation_contract.rs b/src/mir/builder/control_flow/joinir/merge/tests/continuation_contract.rs new file mode 100644 index 00000000..d4120bbd --- /dev/null +++ b/src/mir/builder/control_flow/joinir/merge/tests/continuation_contract.rs @@ -0,0 +1,96 @@ +//! Phase 132-R0 Task 3: Continuation Contract Tests +//! +//! Tests for the continuation contract enforcement in JoinIR merge. +//! +//! # Contract +//! +//! - Router/lowerer declares continuation functions in JoinInlineBoundary +//! - Merge uses structural checks only (no by-name/by-id inference) +//! - Skippable continuation: 1 block + empty instructions + Return only +//! - Non-skippable continuation: Contains other instructions (e.g., TailCall) + +use crate::mir::builder::control_flow::joinir::merge::instruction_rewriter::is_skippable_continuation; +use crate::mir::{BasicBlockId, EffectMask, FunctionSignature, MirFunction, MirInstruction, MirType, ValueId}; + +fn make_function(name: &str) -> MirFunction { + let signature = FunctionSignature { + name: name.to_string(), + params: vec![], + return_type: MirType::Void, + effects: EffectMask::PURE, + }; + MirFunction::new(signature, BasicBlockId(0)) +} + +/// Case A: Pure k_exit with Return only is skippable +/// +/// This is the typical continuation function pattern: +/// - 1 block +/// - No instructions +/// - Return terminator only +#[test] +fn case_a_pure_k_exit_return_is_skippable() { + let mut func = make_function("join_func_2"); + let block = func.blocks.get_mut(&func.entry_block).unwrap(); + block.instructions.clear(); + block.terminator = Some(MirInstruction::Return { value: None }); + assert!(is_skippable_continuation(&func)); +} + +/// Case B: k_exit with TailCall to post_k is NOT skippable +/// +/// When k_exit calls another function (e.g., post_k for post-processing), +/// it is NOT a pure exit stub and must be merged as a real function. +#[test] +fn case_b_k_exit_tailcall_post_k_is_not_skippable() { + let mut func = make_function("join_func_2"); + let block = func.blocks.get_mut(&func.entry_block).unwrap(); + block.instructions.push(MirInstruction::Call { + dst: None, + func: ValueId(0), + callee: None, + args: vec![], + effects: EffectMask::CONTROL, + }); + block.terminator = Some(MirInstruction::Jump { + target: BasicBlockId(1), + }); + assert!(!is_skippable_continuation(&func)); +} + +/// Case C: Multi-block continuation is NOT skippable +/// +/// Even if empty, a continuation with multiple blocks is not a pure exit stub. +#[test] +fn case_c_multi_block_continuation_is_not_skippable() { + let mut func = make_function("join_func_2"); + // Add a second block + let second_block_id = BasicBlockId(1); + let mut second_block = crate::mir::BasicBlock::new(second_block_id); + second_block.terminator = Some(MirInstruction::Return { value: None }); + func.blocks.insert(second_block_id, second_block); + + // Entry block jumps to second block + let entry_block = func.blocks.get_mut(&func.entry_block).unwrap(); + entry_block.instructions.clear(); + entry_block.terminator = Some(MirInstruction::Jump { + target: second_block_id, + }); + + assert!(!is_skippable_continuation(&func)); +} + +/// Case D: Continuation with instructions is NOT skippable +/// +/// Even with Return terminator, any instruction makes it non-skippable. +#[test] +fn case_d_continuation_with_instructions_is_not_skippable() { + let mut func = make_function("join_func_2"); + let block = func.blocks.get_mut(&func.entry_block).unwrap(); + block.instructions.push(MirInstruction::Const { + dst: ValueId(0), + value: crate::mir::types::ConstValue::Integer(42), + }); + block.terminator = Some(MirInstruction::Return { value: None }); + assert!(!is_skippable_continuation(&func)); +} diff --git a/src/mir/builder/control_flow/joinir/merge/tests/mod.rs b/src/mir/builder/control_flow/joinir/merge/tests/mod.rs new file mode 100644 index 00000000..3e4556af --- /dev/null +++ b/src/mir/builder/control_flow/joinir/merge/tests/mod.rs @@ -0,0 +1,5 @@ +//! Phase 132-R0 Task 3: JoinIR Merge Tests +//! +//! Tests for JoinIR merge contracts and invariants. + +pub mod continuation_contract; diff --git a/src/mir/builder/control_flow/joinir/mod.rs b/src/mir/builder/control_flow/joinir/mod.rs index 6bb1c80f..7e4d292f 100644 --- a/src/mir/builder/control_flow/joinir/mod.rs +++ b/src/mir/builder/control_flow/joinir/mod.rs @@ -10,12 +10,12 @@ //! - Control tree capability guard (control_tree_capability_guard.rs) ✅ Phase 112 pub(in crate::mir::builder) mod control_tree_capability_guard; +pub(in crate::mir::builder) mod legacy; // Phase 132-R0 Task 4: Legacy routing isolation pub(in crate::mir::builder) mod loop_context; pub(in crate::mir::builder) mod merge; pub(in crate::mir::builder) mod parity_checker; 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 trace; // Phase 140-P4-A: Re-export for loop_canonicalizer SSOT (crate-wide visibility) diff --git a/src/mir/builder/control_flow/joinir/patterns/exit_binding.rs b/src/mir/builder/control_flow/joinir/patterns/exit_binding.rs index 0fe4ab84..755f7844 100644 --- a/src/mir/builder/control_flow/joinir/patterns/exit_binding.rs +++ b/src/mir/builder/control_flow/joinir/patterns/exit_binding.rs @@ -383,6 +383,9 @@ mod tests { expr_result: None, // Phase 33-14: Add missing field loop_var_name: None, // Phase 33-16: Add missing field carrier_info: None, // Phase 228: Add missing field + continuation_func_ids: std::collections::BTreeSet::from([ + crate::mir::join_ir::JoinFuncId::new(2), + ]), exit_reconnect_mode: crate::mir::join_ir::lowering::carrier_info::ExitReconnectMode::default(), // Phase 131 P1.5 }; diff --git a/src/mir/builder/control_flow/joinir/patterns/exit_binding_applicator.rs b/src/mir/builder/control_flow/joinir/patterns/exit_binding_applicator.rs index af72c79d..cf9bf4db 100644 --- a/src/mir/builder/control_flow/joinir/patterns/exit_binding_applicator.rs +++ b/src/mir/builder/control_flow/joinir/patterns/exit_binding_applicator.rs @@ -137,6 +137,9 @@ mod tests { expr_result: None, // Phase 33-14: Add missing field loop_var_name: None, // Phase 33-16: Add missing field carrier_info: None, // Phase 228: Add missing field + continuation_func_ids: std::collections::BTreeSet::from([ + crate::mir::join_ir::JoinFuncId::new(2), + ]), exit_reconnect_mode: crate::mir::join_ir::lowering::carrier_info::ExitReconnectMode::default(), // Phase 131 P1.5 }; diff --git a/src/mir/join_ir/lowering/carrier_info.rs b/src/mir/join_ir/lowering/carrier_info.rs index e98c48e9..9d03b03d 100644 --- a/src/mir/join_ir/lowering/carrier_info.rs +++ b/src/mir/join_ir/lowering/carrier_info.rs @@ -23,10 +23,13 @@ use crate::mir::ValueId; use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism +use std::collections::BTreeSet; #[cfg(feature = "normalized_dev")] use crate::mir::BindingId; // Phase 76+78: BindingId for promoted carriers +use crate::mir::join_ir::JoinFuncId; + /// Phase 227: CarrierRole - Distinguishes loop state carriers from condition-only carriers /// /// When LoopBodyLocal variables are promoted to carriers, we need to know whether @@ -793,6 +796,16 @@ pub struct JoinFragmentMeta { /// Maps carrier names to their JoinIR-local exit values. /// These go to carrier_inputs for carrier PHI generation. pub exit_meta: ExitMeta, + + /// Phase 132 P1: Continuation contract (SSOT) + /// + /// JoinIR merge must NOT “guess” continuation functions by name/ID. + /// Normalized shadow (and other frontends) must explicitly declare which JoinFuncIds + /// are continuations for the fragment, and merge must follow this contract. + /// + /// Merge may still choose to *skip* some continuation functions if and only if they + /// are structurally “skippable” (pure exit stubs). See merge/instruction_rewriter.rs. + pub continuation_funcs: BTreeSet, } impl JoinFragmentMeta { @@ -803,6 +816,7 @@ impl JoinFragmentMeta { Self { expr_result: Some(expr_result), exit_meta, + continuation_funcs: BTreeSet::new(), } } @@ -813,6 +827,7 @@ impl JoinFragmentMeta { Self { expr_result: None, exit_meta, + continuation_funcs: BTreeSet::new(), } } @@ -821,6 +836,7 @@ impl JoinFragmentMeta { Self { expr_result: None, exit_meta: ExitMeta::empty(), + continuation_funcs: BTreeSet::new(), } } diff --git a/src/mir/join_ir/lowering/inline_boundary.rs b/src/mir/join_ir/lowering/inline_boundary.rs index 600d6a26..e8315023 100644 --- a/src/mir/join_ir/lowering/inline_boundary.rs +++ b/src/mir/join_ir/lowering/inline_boundary.rs @@ -44,7 +44,9 @@ //! ``` use super::carrier_info::{CarrierRole, ExitReconnectMode}; +use crate::mir::join_ir::JoinFuncId; use crate::mir::ValueId; +use std::collections::BTreeSet; /// Explicit binding between JoinIR exit value and host variable /// @@ -258,6 +260,15 @@ pub struct JoinInlineBoundary { /// - `None`: Legacy path (derive carriers from exit_bindings) pub carrier_info: Option, + /// Phase 132 P1: Continuation contract (SSOT) + /// + /// JoinIR merge must not infer/guess continuation functions. The router/lowerer + /// must declare continuation JoinFuncIds here. + /// + /// Merge may still choose to *skip* a continuation function if it is a pure + /// exit stub (structural check), but it must never skip based on name/ID alone. + pub continuation_func_ids: BTreeSet, + /// Phase 131 P1.5: Exit reconnection mode /// /// Controls whether exit values are reconnected via PHI generation (Phi) @@ -269,6 +280,32 @@ pub struct JoinInlineBoundary { } impl JoinInlineBoundary { + /// Phase 132-R0 Task 1: SSOT for default continuation function IDs + /// + /// Returns the default set of continuation functions (k_exit = join_func_2). + /// This is the single source of truth for continuation function identification. + /// + /// # Rationale + /// + /// - Router/lowerer must declare continuation functions explicitly + /// - Merge must NOT infer continuations by name or ID + /// - This method centralizes the default continuation contract + /// + /// # Usage + /// + /// Use this method when constructing JoinInlineBoundary objects: + /// + /// ```ignore + /// JoinInlineBoundary { + /// // ... + /// continuation_func_ids: JoinInlineBoundary::default_continuations(), + /// // ... + /// } + /// ``` + pub fn default_continuations() -> BTreeSet { + BTreeSet::from([JoinFuncId::new(2)]) + } + /// Create a new boundary with input mappings only /// /// This is the common case for loops like Pattern 1 where: @@ -293,6 +330,7 @@ impl JoinInlineBoundary { expr_result: None, // Phase 33-14: Default to carrier-only pattern loop_var_name: None, // Phase 33-16 carrier_info: None, // Phase 228: Default to None + continuation_func_ids: Self::default_continuations(), exit_reconnect_mode: ExitReconnectMode::default(), // Phase 131 P1.5: Default to Phi } } @@ -337,6 +375,7 @@ impl JoinInlineBoundary { expr_result: None, // Phase 33-14 loop_var_name: None, // Phase 33-16 carrier_info: None, // Phase 228 + continuation_func_ids: Self::default_continuations(), exit_reconnect_mode: ExitReconnectMode::default(), // Phase 131 P1.5: Default to Phi } } @@ -398,6 +437,7 @@ impl JoinInlineBoundary { expr_result: None, // Phase 33-14 loop_var_name: None, // Phase 33-16 carrier_info: None, // Phase 228 + continuation_func_ids: Self::default_continuations(), exit_reconnect_mode: ExitReconnectMode::default(), // Phase 131 P1.5: Default to Phi } } @@ -445,6 +485,7 @@ impl JoinInlineBoundary { expr_result: None, // Phase 33-14 loop_var_name: None, // Phase 33-16 carrier_info: None, // Phase 228 + continuation_func_ids: Self::default_continuations(), exit_reconnect_mode: ExitReconnectMode::default(), // Phase 131 P1.5: Default to Phi } } @@ -496,6 +537,7 @@ impl JoinInlineBoundary { expr_result: None, // Phase 33-14 loop_var_name: None, // Phase 33-16 carrier_info: None, // Phase 228 + continuation_func_ids: Self::default_continuations(), exit_reconnect_mode: ExitReconnectMode::default(), // Phase 131 P1.5: Default to Phi } } @@ -554,6 +596,7 @@ impl JoinInlineBoundary { expr_result: None, // Phase 33-14 loop_var_name: None, // Phase 33-16 carrier_info: None, // Phase 228 + continuation_func_ids: Self::default_continuations(), exit_reconnect_mode: ExitReconnectMode::default(), // Phase 131 P1.5: Default to Phi } } diff --git a/src/mir/join_ir/lowering/inline_boundary_builder.rs b/src/mir/join_ir/lowering/inline_boundary_builder.rs index 080efef3..c4d89a7e 100644 --- a/src/mir/join_ir/lowering/inline_boundary_builder.rs +++ b/src/mir/join_ir/lowering/inline_boundary_builder.rs @@ -26,7 +26,9 @@ use super::condition_to_joinir::ConditionBinding; use super::inline_boundary::{JoinInlineBoundary, LoopExitBinding}; +use crate::mir::join_ir::JoinFuncId; use crate::mir::ValueId; +use std::collections::BTreeSet; /// Role of a parameter in JoinIR lowering (Phase 200-A) /// @@ -97,6 +99,7 @@ impl JoinInlineBoundaryBuilder { expr_result: None, loop_var_name: None, carrier_info: None, // Phase 228: Initialize as None + continuation_func_ids: JoinInlineBoundary::default_continuations(), exit_reconnect_mode: ExitReconnectMode::default(), // Phase 131 P1.5 }, }