feat(joinir): Phase 34-7.5 helpers + Phase 34-8 Break/Continue implementation

Phase 34-7.5: Code organization improvements
- Added type conversion helpers (as_cont/as_func) in join_ir/mod.rs
- Enhanced docstrings for JoinCall/JoinJump with usage examples
- Improved error messages in join_ir_vm_bridge.rs

JoinIrFrontendTestRunner box implementation
- Created src/tests/helpers/joinir_frontend.rs (119 lines)
- Reduced test code by 83% (70 lines → 12 lines per test)
- 26% overall reduction in test file (284 → 209 lines)

Phase 34-8: Break/Continue pattern implementation
- Extended ast_lowerer.rs (+630 lines)
  - lower_loop_break_pattern(): Break as Jump (early return)
  - lower_loop_continue_pattern(): Continue as Select + Call
  - Added Bool literal support in extract_value()
- Created 2 fixtures: loop_frontend_{break,continue}.program.json
- Added 2 A/B tests (all 6 Phase 34 tests PASS)

Technical achievements:
- Break = Jump (early return pattern)
- Continue = Select + Call (NOT Jump) - critical discovery
- 3-function structure sufficient (no k_continue needed)
- SSA-style re-assignment with natural var_map updates

Test results:
 joinir_frontend_if_select_simple_ab_test
 joinir_frontend_if_select_local_ab_test
 joinir_frontend_json_shape_read_value_ab_test
 joinir_frontend_loop_simple_ab_test
 joinir_frontend_loop_break_ab_test
 joinir_frontend_loop_continue_ab_test

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-28 01:02:49 +09:00
parent a22726934d
commit 853d80ba74
9 changed files with 1292 additions and 177 deletions

View File

@ -52,6 +52,23 @@ impl JoinFuncId {
pub fn new(id: u32) -> Self {
JoinFuncId(id)
}
/// JoinFuncId を JoinContId に変換
///
/// # Use Case
/// Jump 命令で関数を continuation として使う場合
/// ```rust
/// let func_id = JoinFuncId(42);
/// let cont_id = func_id.as_cont();
/// Jump { cont: cont_id, args: vec![], cond: None }
/// ```
///
/// # Phase 34-7 Note
/// JoinFuncId と JoinContId は別の newtype だが、内部的には同じ u32 ID を共有する。
/// この変換は型レベルでの役割の明示(関数 vs 継続)を可能にする。
pub fn as_cont(self) -> JoinContId {
JoinContId(self.0)
}
}
/// 継続join / ループ step / exit continuationを識別するID
@ -62,6 +79,21 @@ impl JoinContId {
pub fn new(id: u32) -> Self {
JoinContId(id)
}
/// JoinContId を JoinFuncId に変換
///
/// # Use Case
/// 継続 ID を関数 ID として参照する場合JoinModule の functions map でルックアップ時など)
/// ```rust
/// let cont_id = JoinContId(42);
/// let func = join_module.functions.get(&cont_id.as_func())?;
/// ```
///
/// # Phase 34-7 Note
/// JoinIR では継続も関数として実装されるため、この変換が必要になる。
pub fn as_func(self) -> JoinFuncId {
JoinFuncId(self.0)
}
}
/// 変数IDPhase 26-H では MIR の ValueId を再利用)
@ -179,7 +211,31 @@ pub struct MergePair {
/// JoinIR 命令セット(最小版)
#[derive(Debug, Clone)]
pub enum JoinInst {
/// 通常の関数呼び出し: f(args..., k_next)
/// 通常の関数呼び出し(末尾再帰): f(args..., k_next)
///
/// # Semantics
/// - 他の JoinIR 関数を呼び出すMIR の Call に変換)
/// - ループでは末尾再帰として使うのが典型的
///
/// # MIR 変換
/// - `MirInstruction::Call { func, args, ... }` を生成
///
/// # Constraints (Phase 31/34 時点)
/// - **k_next は常に None にすること!**
/// JoinIR→MIR bridge が `k_next: Some(...)` 未対応
/// → エラー: "Call with k_next is not yet supported"
/// - 典型的な使い方: `Call { func, args, k_next: None, dst: Some(...) }`
///
/// # Loop Pattern での使い方 (Phase 34-7)
/// ```rust
/// // ✅ 正解: 末尾再帰
/// Call {
/// func: loop_step_id,
/// args: vec![i_next, acc_next, n],
/// k_next: None, // ⚠️ 必須: None にすること
/// dst: Some(result),
/// }
/// ```
Call {
func: JoinFuncId,
args: Vec<VarId>,
@ -188,7 +244,46 @@ pub enum JoinInst {
dst: Option<VarId>,
},
/// 継続呼び出し(join / exit 継続など
/// 継続呼び出し(早期 return / exit 継続)
///
/// # Semantics
/// - **「早期 return」条件付き関数脱出として使う**
/// - cond=Some(v): v が true なら cont に Jump、false なら次の命令へ
/// - cond=None: 無条件 Jump
///
/// # MIR 変換
/// - cond=Some(v): `Branch(v, exit_block[Return], continue_block)` を生成
/// - exit_block: cont 関数を Call して Return
/// - continue_block: 次の JoinInst に続く
/// - cond=None: 無条件に cont を Call して Return
///
/// # Loop Pattern での使い方 (Phase 34-7)
/// ```rust
/// // ✅ 正解: 条件付き早期 return
/// Jump {
/// cont: k_exit_id.as_cont(),
/// args: vec![acc],
/// cond: Some(exit_cond), // exit_cond が true なら k_exit へ
/// }
/// // ↑ exit_cond が false なら次の命令body 処理)へ進む
///
/// // ❌ 間違い: Call で条件分岐しようとする
/// Call {
/// func: k_exit_id,
/// cond: Some(exit_cond), // こんなフィールドはない!
/// }
/// ```
///
/// # 典型的なパターン
/// ```text
/// loop_step(i, acc, n):
/// exit_cond = !(i < n)
/// Jump(k_exit, [acc], cond=exit_cond) // 🔑 早期 return
/// // ↓ Jump で抜けなかった場合のみ実行
/// acc_next = acc + 1
/// i_next = i + 1
/// Call(loop_step, [i_next, acc_next, n]) // 🔑 末尾再帰
/// ```
Jump {
cont: JoinContId,
args: Vec<VarId>,