diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 46691e72..5a4a1ed5 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -12,7 +12,7 @@ - **25.x**: Stage0/Stage1/Stage‑B / Selfhost ラインのブートストラップと LoopForm v2 / LoopSSA v2 まわりの整備。 - **25.1 系**: Stage‑B / Stage‑1 / selfhost 向けに、Rust MIR / LoopForm v2 / LoopSSA v2 を段階的に整える長期ライン。 - **26-F / 26-G**: Exit PHI / ExitLiveness 用の 4箱構成(LoopVarClassBox / LoopExitLivenessBox / BodyLocalPhiBuilder / PhiInvariantsBox)と MirScanExitLiveness の準備。 - - **26-H / 27.x(New)**: JoinIR 設計+ミニ実験フェーズ → minimal/skip_ws/FuncScanner.trim までを対象に、制御構造を関数呼び出しに正規化する IR とランナーを段階的に整備中(27.4 で Header φ を LoopHeaderShape 化、27.5 で Exit φ の意味を LoopExitShape として固定済み。27.6-1/2/3 で ExitPhiBuilder 側にトグル付きバイパスを入れて A/B 観測まで完了、seal_phis と Header φ バイパスの整合性は別フェーズで refinement 予定)。 + - **26-H / 27.x(New)**: JoinIR 設計+ミニ実験フェーズ → minimal/skip_ws/FuncScanner.trim/Stage‑1 UsingResolver minimal/FuncScanner.append_defs minimal までを対象に、制御構造を関数呼び出しに正規化する IR とランナーを段階的に整備中(27.4 で Header φ を LoopHeaderShape 化、27.5 で Exit φ の意味を LoopExitShape として固定済み。27.6-1/2/3 で ExitPhiBuilder 側にトグル付きバイパスを入れて A/B 観測まで完了、seal_phis と Header φ バイパスの整合性は別フェーズで refinement 予定。27.8〜27.11 で skip_ws/trim を Shared Builder Pattern+MIR-based lowering に移行し、27.12/27.13 で Stage‑1 UsingResolver minimal loop も同じ型に乗せ、27.14 では FuncScanner.append_defs 用の lowering+minimal .hako+auto_lowering テストまで整備済み。短期フェーズ残りは JoinIR runner の命令セット整理とスモーク固め)。 - Rust 側: - LoopForm v2 + ControlForm + Conservative PHI は、代表テスト(Stage‑1 UsingResolver / Stage‑B 最小ループ)ではほぼ安定。 - 静的メソッド呼び出し規約と `continue` 絡みの PHI は 25.1m までで根治済み。 diff --git a/apps/tests/funcscanner_append_defs_minimal.hako b/apps/tests/funcscanner_append_defs_minimal.hako new file mode 100644 index 00000000..bcd03f48 --- /dev/null +++ b/apps/tests/funcscanner_append_defs_minimal.hako @@ -0,0 +1,34 @@ +// funcscanner_append_defs_minimal.hako +// Phase 27.14: FuncScannerBox._append_defs minimal loop for JoinIR testing +// +// Purpose: Minimal test case for JoinIR lowering without complex dependencies +// - No `using` statements (avoids parser issues) +// - No FileBox/IO operations (pure loop structure) +// - Simple array append operation +// +// This file is designed for auto_lowering test to verify JoinIR structure: +// - Function signature: FuncScannerBox._append_defs/2 +// - Loop structure: dst.push(defs_box.get(i)) traversal with i < n condition +// - Pinned: dst, defs_box, n +// - Carrier: i +// - Exit: none (void return, dst modified in-place) + +static box FuncScannerBox { + _append_defs(dst, defs_box) { + if defs_box == null { return } + + local i = 0 + local n = defs_box.length() + loop(i < n) { + local item = defs_box.get(i) + dst.push(item) + i = i + 1 + } + } +} + +static box Main { + main() { + return 0 + } +} diff --git a/docs/private b/docs/private index 705d433a..eef3d260 160000 --- a/docs/private +++ b/docs/private @@ -1 +1 @@ -Subproject commit 705d433a9949450e6b04305adba621ba2da1abfe +Subproject commit eef3d26002eafa056c3ea0234f54d8994813b272 diff --git a/src/mir/join_ir/lowering/common.rs b/src/mir/join_ir/lowering/common.rs index 062265b7..4697cc82 100644 --- a/src/mir/join_ir/lowering/common.rs +++ b/src/mir/join_ir/lowering/common.rs @@ -99,6 +99,44 @@ pub fn has_binop(query: &MirQueryBox, bb: BasicBlockId, op: BinaryOp) -> bool { }) } +/// Check if a basic block contains `BoxCall { method }` +/// +/// Returns `true` if the block contains a BoxCall instruction with the specified method name. +/// Note: This cannot distinguish between different box types (e.g., ArrayBox.get vs StringBox.get) +/// since MIR's BoxCall uses ValueId instead of box_name. +/// +/// For more precise type checking, use TypeRegistry (future enhancement). +/// +/// # Example +/// ```ignore +/// // Check if entry block contains ArrayBox.length() +/// // (Note: will also match StringBox.length() if it exists) +/// if has_array_method(&query, entry_id, "length") { +/// // ... +/// } +/// ``` +pub fn has_array_method(query: &MirQueryBox, bb: BasicBlockId, method: &str) -> bool { + // Note: This is the same as has_string_method() since MIR BoxCall doesn't include box_name + // Future enhancement: Use TypeRegistry to check box_val's type + has_string_method(query, bb, method) +} + +/// Check if a basic block contains a loop increment pattern (`i + 1`) +/// +/// Returns `true` if the block contains a `BinOp::Add` instruction. +/// This is a convenience wrapper for `has_binop(query, bb, BinaryOp::Add)`. +/// +/// # Example +/// ```ignore +/// // Check if loop body contains i + 1 +/// if has_loop_increment(&query, loop_body_id) { +/// // ... +/// } +/// ``` +pub fn has_loop_increment(query: &MirQueryBox, bb: BasicBlockId) -> bool { + has_binop(query, bb, BinaryOp::Add) +} + /// Log fallback to handwritten lowering with reason /// /// Prints a diagnostic message when MIR-based lowering falls back to handwritten version. diff --git a/src/mir/join_ir/lowering/funcscanner_append_defs.rs b/src/mir/join_ir/lowering/funcscanner_append_defs.rs new file mode 100644 index 00000000..25c19e18 --- /dev/null +++ b/src/mir/join_ir/lowering/funcscanner_append_defs.rs @@ -0,0 +1,321 @@ +//! Phase 27.14: FuncScannerBox._append_defs loop の JoinIR lowering +//! +//! 目的: FuncScanner の最も簡単な配列結合ループを JoinIR に変換 +//! +//! ## 対象ループ +//! - ファイル: `lang/src/compiler/entry/func_scanner.hako` +//! - 関数: `FuncScannerBox._append_defs(dst, defs_box)` +//! - 行数: 293-300 +//! +//! ## ループ構造 +//! ```hako +//! method _append_defs(dst, defs_box) { +//! if defs_box == null { return } +//! local i = 0 +//! loop(i < defs_box.length()) { +//! dst.push(defs_box.get(i)) +//! i = i + 1 +//! } +//! } +//! ``` +//! +//! ## LoopForm ケース: Case A (動的条件 `i < defs_box.length()`) +//! +//! ## Pinned / Carrier / Exit +//! - **Pinned**: `dst` (ArrayBox), `defs_box` (ArrayBox), `n` (Integer = defs_box.length()) +//! - **Carrier**: `i` (Integer) +//! - **Exit**: none (void return, dst は破壊的変更) +//! +//! ## 想定 JoinIR 構造 +//! ```text +//! fn append_defs_entry(dst, defs_box, n) -> void { +//! let i_init = 0; +//! loop_step(dst, defs_box, n, i_init) +//! } +//! +//! fn loop_step(dst, defs_box, n, i) -> void { +//! if i >= n { return } +//! let item = defs_box.get(i) +//! dst.push(item) +//! let next_i = i + 1 +//! loop_step(dst, defs_box, n, next_i) +//! } +//! ``` + +use crate::mir::join_ir::lowering::common::{ + dispatch_lowering, ensure_entry_has_succs, has_const_int, + has_array_method, has_loop_increment, log_fallback +}; +use crate::mir::join_ir::lowering::value_id_ranges::funcscanner_append_defs as vid; +use crate::mir::join_ir::{JoinModule}; +use crate::mir::query::{MirQuery, MirQueryBox}; + +/// Phase 27.14: FuncScannerBox._append_defs の JoinIR lowering(public dispatcher) +/// +/// 環境変数 `NYASH_JOINIR_LOWER_FROM_MIR=1` に応じて、 +/// MIR-based 版または handwritten 版を選択する。 +/// +/// ## トグル制御: +/// - **OFF (デフォルト)**: `lower_handwritten()` を使用 +/// - **ON**: `lower_from_mir()` を使用 +/// +/// ## Shared Builder Pattern +/// 両方の実装が `build_funcscanner_append_defs_joinir()` を呼び出す共通パターン。 +pub fn lower_funcscanner_append_defs_to_joinir(module: &crate::mir::MirModule) -> Option { + dispatch_lowering( + "funcscanner_append_defs", + module, + lower_from_mir, + lower_handwritten, + ) +} + +/// Phase 27.14: Common JoinIR builder for FuncScannerBox._append_defs +/// +/// This function generates the JoinIR for the append_defs loop, shared by both: +/// - lower_handwritten (always uses this) +/// - lower_from_mir (uses this after CFG sanity checks pass) +/// +/// ## 簡略化方針 +/// Phase 27.14 の最小実装として、最も単純な JoinIR を生成する: +/// - ArrayBox.length() → dst.push(item) の基本パターン +/// - null チェックは省略(MIR 側で既に処理済み前提) +fn build_funcscanner_append_defs_joinir(module: &crate::mir::MirModule) -> Option { + use crate::mir::join_ir::*; + + // Phase 27.14: ターゲット関数が存在するかチェック + let _target_func = module.functions.get("FuncScannerBox._append_defs/2")?; + + eprintln!("[joinir/funcscanner_append_defs/build] Phase 27.14 implementation"); + eprintln!("[joinir/funcscanner_append_defs/build] Generating JoinIR for _append_defs loop"); + eprintln!("[joinir/funcscanner_append_defs/build] Using ValueId range: 9000-10999 (via value_id_ranges)"); + + // Step 1: JoinModule を構築 + let mut join_module = JoinModule::new(); + + // append_defs_entry 関数(entry): + // fn append_defs_entry(dst, defs_box, n) -> void { + // let i_init = 0; + // loop_step(dst, defs_box, n, i_init) + // } + let entry_id = JoinFuncId::new(0); + let dst_param = vid::entry(0); // 9000 + let defs_box_param = vid::entry(1); // 9001 + let n_param = vid::entry(2); // 9002 + + let mut entry_func = JoinFunction::new( + entry_id, + "append_defs_entry".to_string(), + vec![dst_param, defs_box_param, n_param], + ); + + let i_init = vid::entry(10); // 9010 + + // i_init = 0 + entry_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: i_init, + value: ConstValue::Integer(0), + })); + + // loop_step(dst, defs_box, n, i_init) + let loop_step_id = JoinFuncId::new(1); + entry_func.body.push(JoinInst::Call { + func: loop_step_id, + args: vec![dst_param, defs_box_param, n_param, i_init], + k_next: None, + dst: None, + }); + + join_module.entry = Some(entry_id); + join_module.add_function(entry_func); + + // Phase 27.14: loop_step の Pinned/Carrier 構造を明示 + // FuncScanner _append_defs ループの場合: + // - Pinned: dst (ArrayBox), defs_box (ArrayBox), n (Integer) + // - Carrier: i (Integer) + // - Exit: none (void return) + let dst_loop = vid::loop_step(0); // 10000 - Pinned + let defs_box_loop = vid::loop_step(1); // 10001 - Pinned + let n_loop = vid::loop_step(2); // 10002 - Pinned + let i_loop = vid::loop_step(3); // 10003 - Carrier + + let _header_shape = LoopHeaderShape::new_manual( + vec![dst_loop, defs_box_loop, n_loop], // Pinned + vec![i_loop], // Carrier + ); + + // loop_step 関数: + // fn loop_step(dst, defs_box, n, i) -> void { + // if i >= n { return } + // let item = defs_box.get(i) + // dst.push(item) + // let next_i = i + 1 + // loop_step(dst, defs_box, n, next_i) + // } + let mut loop_step_func = JoinFunction::new( + loop_step_id, + "loop_step".to_string(), + vec![dst_loop, defs_box_loop, n_loop, i_loop], + ); + + let cmp_result = vid::loop_step(10); // 10010 + let item_value = vid::loop_step(11); // 10011 + let next_i = vid::loop_step(12); // 10012 + let const_1 = vid::loop_step(13); // 10013 + + // cmp_result = (i >= n) + loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_result, + op: CompareOp::Ge, + lhs: i_loop, + rhs: n_loop, + })); + + // Phase 27.14: Exit φ の意味を LoopExitShape で明示 + // FuncScanner _append_defs ループ脱出時は void 返却(dst は破壊的変更済み) + let _exit_shape = LoopExitShape::new_manual(vec![]); // exit_args = [] (void) + + // if i >= n { return } (void) + loop_step_func.body.push(JoinInst::Jump { + cont: JoinContId::new(0), + args: vec![], // ← LoopExitShape.exit_args に対応 (void) + cond: Some(cmp_result), + }); + + // item = defs_box.get(i) + loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(item_value), + box_name: "ArrayBox".to_string(), + method: "get".to_string(), + args: vec![defs_box_loop, i_loop], + })); + + // dst.push(item) - 破壊的変更(戻り値なし) + loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: None, // push は戻り値なし + box_name: "ArrayBox".to_string(), + method: "push".to_string(), + args: vec![dst_loop, item_value], + })); + + // const_1 = 1 + loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: const_1, + value: ConstValue::Integer(1), + })); + + // next_i = i + 1 + loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: next_i, + op: BinOpKind::Add, + lhs: i_loop, + rhs: const_1, + })); + + // loop_step(dst, defs_box, n, next_i) - tail recursion + loop_step_func.body.push(JoinInst::Call { + func: loop_step_id, + args: vec![dst_loop, defs_box_loop, n_loop, next_i], + k_next: None, + dst: None, + }); + + join_module.add_function(loop_step_func); + + eprintln!("[joinir/funcscanner_append_defs/build] ✅ JoinIR construction completed"); + eprintln!("[joinir/funcscanner_append_defs/build] Functions: {}", join_module.functions.len()); + + Some(join_module) +} + +/// Phase 27.14: MIR-based lowering for FuncScannerBox._append_defs +/// +/// CFG sanity checks + MIR パターンマッチング → 成功なら `build_funcscanner_append_defs_joinir()` 呼び出し +/// +/// ## CFG Sanity Checks (軽量パターンマッチ): +/// 1. Entry block に後続がある +/// 2. Entry block 付近に以下の命令がある: +/// - `Const { value: Integer(0) }` (初期 i = 0) +/// - `BoxCall { box_name: "ArrayBox", method: "length" }` (n = defs_box.length()) +/// 3. ループ本体付近に: +/// - `BoxCall { box_name: "ArrayBox", method: "get" }` (defs_box.get(i)) +/// - `BoxCall { box_name: "ArrayBox", method: "push" }` (dst.push(item)) +/// - `BinOp { op: Add }` (next_i = i + 1) +/// +/// ## Graceful Degradation +/// 上記パターンが検出できない場合は `log_fallback()` → `lower_handwritten()` に戻る。 +fn lower_from_mir(module: &crate::mir::MirModule) -> Option { + eprintln!("[joinir/funcscanner_append_defs/mir] Starting MIR-based lowering"); + + // Step 1: FuncScannerBox._append_defs/2 を探す + let target_func = module.functions.get("FuncScannerBox._append_defs/2")?; + + eprintln!("[joinir/funcscanner_append_defs/mir] Found FuncScannerBox._append_defs/2"); + eprintln!("[joinir/funcscanner_append_defs/mir] MIR blocks: {}", target_func.blocks.len()); + + // Step 2: MirQueryBox を作成 + let query = MirQueryBox::new(target_func); + let entry = target_func.entry_block; + + // CFG Check 1: Entry block has successors + if !ensure_entry_has_succs(&query, entry) { + log_fallback("funcscanner_append_defs", "entry block has no successors"); + return lower_handwritten(module); + } + + // CFG Check 2: Entry block contains expected patterns + // Pattern 1: i = 0 (初期化) + if !has_const_int(&query, entry, 0) { + log_fallback("funcscanner_append_defs", "Const(0) not found in entry block"); + return lower_handwritten(module); + } + + // Pattern 2: defs_box.length() の検出 + // Check entry block and its immediate successors for length() call + let has_length_call = has_array_method(&query, entry, "length") + || query.succs(entry).iter().any(|&succ| has_array_method(&query, succ, "length")); + + if !has_length_call { + log_fallback("funcscanner_append_defs", "ArrayBox.length() not found in entry or successors"); + return lower_handwritten(module); + } + + // Pattern 3: ループ本体での配列操作検出 + // Check all blocks for array operations (get/push) and loop increment + let all_blocks: Vec<_> = target_func.blocks.keys().copied().collect(); + + let has_get_call = all_blocks.iter().any(|&bb| has_array_method(&query, bb, "get")); + if !has_get_call { + log_fallback("funcscanner_append_defs", "ArrayBox.get() not found in function body"); + return lower_handwritten(module); + } + + let has_push_call = all_blocks.iter().any(|&bb| has_array_method(&query, bb, "push")); + if !has_push_call { + log_fallback("funcscanner_append_defs", "ArrayBox.push() not found in function body"); + return lower_handwritten(module); + } + + let has_increment = all_blocks.iter().any(|&bb| has_loop_increment(&query, bb)); + if !has_increment { + log_fallback("funcscanner_append_defs", "loop increment (i + 1) not found in function body"); + return lower_handwritten(module); + } + + eprintln!("[joinir/funcscanner_append_defs/mir] CFG sanity checks passed ✅"); + eprintln!("[joinir/funcscanner_append_defs/mir] Found: length(), get(), push(), i+1"); + + // Phase 27.14: Generate JoinIR using shared builder + // CFG checks passed, so we can use build_funcscanner_append_defs_joinir() directly + eprintln!("[joinir/funcscanner_append_defs/mir] Calling build_funcscanner_append_defs_joinir() after CFG validation"); + build_funcscanner_append_defs_joinir(module) +} + +/// Phase 27.14: Handwritten lowering wrapper for FuncScannerBox._append_defs +/// +/// This is a thin wrapper that calls the shared build_funcscanner_append_defs_joinir() function. +/// Maintains the handwritten lowering path as the baseline reference. +fn lower_handwritten(module: &crate::mir::MirModule) -> Option { + eprintln!("[joinir/funcscanner_append_defs/handwritten] Using handwritten lowering path"); + build_funcscanner_append_defs_joinir(module) +} diff --git a/src/mir/join_ir/lowering/funcscanner_trim.rs b/src/mir/join_ir/lowering/funcscanner_trim.rs index 7b84e6e7..14e1c1bf 100644 --- a/src/mir/join_ir/lowering/funcscanner_trim.rs +++ b/src/mir/join_ir/lowering/funcscanner_trim.rs @@ -67,7 +67,7 @@ pub fn lower_funcscanner_trim_to_joinir(module: &crate::mir::MirModule) -> Optio /// This function generates the JoinIR for trim/1, shared by both: /// - lower_trim_handwritten (always uses this) /// - lower_trim_from_mir (uses this after CFG sanity checks pass) -fn build_trim_joinir(module: &crate::mir::MirModule) -> Option { +fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option { // Step 1: "FuncScannerBox.trim/1" を探す let target_func = module.functions.get("FuncScannerBox.trim/1")?; @@ -515,11 +515,11 @@ fn build_trim_joinir(module: &crate::mir::MirModule) -> Option { /// Phase 27.11: Handwritten lowering wrapper for FuncScannerBox.trim/1 /// -/// This is a thin wrapper that calls the shared build_trim_joinir() function. +/// This is a thin wrapper that calls the shared build_funcscanner_trim_joinir() function. /// Maintains the handwritten lowering path as the baseline reference. fn lower_trim_handwritten(module: &crate::mir::MirModule) -> Option { eprintln!("[joinir/trim/handwritten] Using handwritten lowering path"); - build_trim_joinir(module) + build_funcscanner_trim_joinir(module) } /// Phase 27.9: MIR-based lowering for FuncScannerBox.trim/1 @@ -561,7 +561,7 @@ fn lower_trim_from_mir(module: &crate::mir::MirModule) -> Option { eprintln!("[joinir/trim/mir] CFG sanity checks passed ✅"); // Phase 27.11: Generate JoinIR using shared builder - // CFG checks passed, so we can use build_trim_joinir() directly - eprintln!("[joinir/trim/mir] Calling build_trim_joinir() after CFG validation"); - build_trim_joinir(module) + // CFG checks passed, so we can use build_funcscanner_trim_joinir() directly + eprintln!("[joinir/trim/mir] Calling build_funcscanner_trim_joinir() after CFG validation"); + build_funcscanner_trim_joinir(module) } diff --git a/src/mir/join_ir/lowering/mod.rs b/src/mir/join_ir/lowering/mod.rs index 780c9cd8..9667a4cb 100644 --- a/src/mir/join_ir/lowering/mod.rs +++ b/src/mir/join_ir/lowering/mod.rs @@ -11,15 +11,18 @@ //! - `skip_ws.rs`: Main.skip/1 の空白スキップ lowering(手書き版+MIR自動解析版) //! - `funcscanner_trim.rs`: FuncScannerBox.trim/1 の trim lowering //! - `stage1_using_resolver.rs`: Stage1UsingResolverBox.resolve_for_source entries loop lowering(Phase 27.12) +//! - `funcscanner_append_defs.rs`: FuncScannerBox._append_defs/2 の配列結合 lowering(Phase 27.14) pub mod common; pub mod value_id_ranges; +pub mod funcscanner_append_defs; pub mod funcscanner_trim; pub mod min_loop; pub mod skip_ws; pub mod stage1_using_resolver; // Re-export public lowering functions +pub use funcscanner_append_defs::lower_funcscanner_append_defs_to_joinir; pub use funcscanner_trim::lower_funcscanner_trim_to_joinir; pub use min_loop::lower_min_loop_to_joinir; pub use skip_ws::lower_skip_ws_to_joinir; diff --git a/src/mir/join_ir/lowering/value_id_ranges.rs b/src/mir/join_ir/lowering/value_id_ranges.rs index 8fc18a31..0c3a0804 100644 --- a/src/mir/join_ir/lowering/value_id_ranges.rs +++ b/src/mir/join_ir/lowering/value_id_ranges.rs @@ -5,12 +5,13 @@ //! //! ## Current Allocations //! -//! | Module | Range | Entry | Loop | Notes | -//! |-----------------------|-----------|-------|-------|-------| -//! | min_loop | 1000-2999 | 1000+ | 2000+ | Minimal loop test | -//! | skip_ws | 3000-4999 | 3000+ | 4000+ | Skip whitespace | -//! | funcscanner_trim | 5000-6999 | 5000+ | 6000+ | Trim whitespace | -//! | stage1_using_resolver | 7000-8999 | 7000+ | 8000+ | Stage-1 using resolver | +//! | Module | Range | Entry | Loop | Notes | +//! |-------------------------|------------|--------|--------|-------| +//! | min_loop | 1000-2999 | 1000+ | 2000+ | Minimal loop test | +//! | skip_ws | 3000-4999 | 3000+ | 4000+ | Skip whitespace | +//! | funcscanner_trim | 5000-6999 | 5000+ | 6000+ | Trim whitespace | +//! | stage1_using_resolver | 7000-8999 | 7000+ | 8000+ | Stage-1 using resolver | +//! | funcscanner_append_defs | 9000-10999 | 9000+ | 10000+ | FuncScanner append defs | //! //! ## Usage Example //! @@ -46,6 +47,9 @@ pub mod base { /// stage1_using_resolver: Stage-1 using resolver entries loop (7000-8999) pub const STAGE1_USING_RESOLVER: u32 = 7000; + + /// funcscanner_append_defs: FuncScanner append defs loop (9000-10999) + pub const FUNCSCANNER_APPEND_DEFS: u32 = 9000; } /// Helper function to create ValueId from base + offset @@ -128,31 +132,67 @@ pub mod stage1_using_resolver { } } +/// ValueId helpers for funcscanner_append_defs lowering module +pub mod funcscanner_append_defs { + use super::{base, id}; + use crate::mir::ValueId; + + /// Entry function ValueIds (9000-9999) + #[inline] + pub const fn entry(offset: u32) -> ValueId { + id(base::FUNCSCANNER_APPEND_DEFS, offset) + } + + /// Loop function ValueIds (10000-10999) + #[inline] + pub const fn loop_step(offset: u32) -> ValueId { + id(base::FUNCSCANNER_APPEND_DEFS, 1000 + offset) + } +} + #[cfg(test)] mod tests { use super::*; + /// Macro to test ValueId range boundaries for a lowering module + /// + /// Verifies that entry(0) and loop_step(999) produce the expected ValueIds + /// based on the module's allocated range. + macro_rules! test_value_id_range { + ($module:ident, $entry_base:expr, $loop_base:expr) => { + assert_eq!($module::entry(0).0, $entry_base, + "{} entry(0) should be {}", stringify!($module), $entry_base); + assert_eq!($module::loop_step(999).0, $loop_base + 999, + "{} loop_step(999) should be {}", stringify!($module), $loop_base + 999); + }; + } + #[test] fn test_value_id_ranges_no_overlap() { - // min_loop: 1000-2999 - assert_eq!(min_loop::entry(0).0, 1000); - assert_eq!(min_loop::loop_step(999).0, 2999); + // Test each module's range boundaries + test_value_id_range!(min_loop, 1000, 2000); + test_value_id_range!(skip_ws, 3000, 4000); + test_value_id_range!(funcscanner_trim, 5000, 6000); + test_value_id_range!(stage1_using_resolver, 7000, 8000); + test_value_id_range!(funcscanner_append_defs, 9000, 10000); - // skip_ws: 3000-4999 - assert_eq!(skip_ws::entry(0).0, 3000); - assert_eq!(skip_ws::loop_step(999).0, 4999); + // Automated overlap detection + // Each range is 2000 units: (base, base+1999) + let ranges = vec![ + (1000, 2999), // min_loop + (3000, 4999), // skip_ws + (5000, 6999), // funcscanner_trim + (7000, 8999), // stage1_using_resolver + (9000, 10999), // funcscanner_append_defs + ]; - // funcscanner_trim: 5000-6999 - assert_eq!(funcscanner_trim::entry(0).0, 5000); - assert_eq!(funcscanner_trim::loop_step(999).0, 6999); - - // stage1_using_resolver: 7000-8999 - assert_eq!(stage1_using_resolver::entry(0).0, 7000); - assert_eq!(stage1_using_resolver::loop_step(999).0, 8999); - - // Verify no overlaps - assert!(min_loop::loop_step(999).0 < skip_ws::entry(0).0); - assert!(skip_ws::loop_step(999).0 < funcscanner_trim::entry(0).0); - assert!(funcscanner_trim::loop_step(999).0 < stage1_using_resolver::entry(0).0); + // Verify no overlaps between consecutive ranges + for i in 0..ranges.len() - 1 { + let (_, end_i) = ranges[i]; + let (start_next, _) = ranges[i + 1]; + assert!(end_i < start_next, + "Overlap detected: range {} ends at {} but range {} starts at {}", + i, end_i, i + 1, start_next); + } } } diff --git a/src/tests/mir_joinir_funcscanner_append_defs.rs b/src/tests/mir_joinir_funcscanner_append_defs.rs new file mode 100644 index 00000000..c99400f9 --- /dev/null +++ b/src/tests/mir_joinir_funcscanner_append_defs.rs @@ -0,0 +1,131 @@ +// mir_joinir_funcscanner_append_defs.rs +// Phase 27.14: FuncScannerBox._append_defs minimal loop JoinIR変換テスト +// +// 目的: +// - FuncScannerBox._append_defs の配列結合ループ(lines 293-300)の JoinIR 変換動作確認 +// - LoopForm Case A (`loop(i < n)`) パターンの変換検証 +// - Pinned/Carrier/Exit 設計の実装確認 +// +// 実行条件: +// - デフォルトでは #[ignore] にしておいて手動実行用にする +// - 環境変数 NYASH_JOINIR_EXPERIMENT=1 で実験モード有効化 +// +// Phase 27.14 設計: +// - Pinned: dst (ArrayBox), defs_box (ArrayBox), n (Integer) +// - Carrier: i (Integer) +// - Exit: none (void return, dst は破壊的変更) +// +// LoopForm Case A: +// - 動的条件: `loop(i < n)` +// - break: なし(常に i < n までループ) +// - continue: `i = i + 1` で統一 + +use crate::ast::ASTNode; +use crate::mir::join_ir::lowering::funcscanner_append_defs::lower_funcscanner_append_defs_to_joinir; +use crate::mir::join_ir::*; +use crate::mir::{MirCompiler, ValueId}; +use crate::parser::NyashParser; + +#[test] +#[ignore] // 手動実行用(Phase 27.14 実験段階) +fn mir_joinir_funcscanner_append_defs_auto_lowering() { + // Phase 27.14: FuncScannerBox._append_defs の MIR → JoinIR 自動変換 + + // 環境変数トグルチェック + if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") { + eprintln!("[joinir/funcscanner_append_defs] NYASH_JOINIR_EXPERIMENT=1 not set, skipping auto-lowering test"); + return; + } + + // Step 1: MIR までコンパイル + // Phase 27.14: Minimal .hako file to avoid complex dependencies + // Stage-3 parser を有効化(local キーワード対応) + std::env::set_var("NYASH_PARSER_STAGE3", "1"); + std::env::set_var("HAKO_PARSER_STAGE3", "1"); + + let test_file = "apps/tests/funcscanner_append_defs_minimal.hako"; + let src = std::fs::read_to_string(test_file) + .unwrap_or_else(|_| panic!("Failed to read {}", test_file)); + + let ast: ASTNode = NyashParser::parse_from_string(&src) + .expect("funcscanner_append_defs: parse failed"); + + let mut mc = MirCompiler::with_options(false); + let compiled = mc.compile(ast).expect("funcscanner_append_defs: MIR compile failed"); + + eprintln!( + "[joinir/funcscanner_append_defs] MIR module compiled, {} functions", + compiled.module.functions.len() + ); + + // Step 2: MIR → JoinIR 自動変換 + let join_module = lower_funcscanner_append_defs_to_joinir(&compiled.module) + .expect("Phase 27.14: JoinIR construction should succeed"); + + eprintln!("[joinir/funcscanner_append_defs] JoinIR module generated:"); + eprintln!("{:#?}", join_module); + + // Step 3: 妥当性検証(Phase 27.14) + assert_eq!(join_module.functions.len(), 2, "Expected 2 functions (append_defs_entry + loop_step)"); + + let entry_id = JoinFuncId::new(0); + let loop_step_id = JoinFuncId::new(1); + + // append_defs_entry 関数の検証 + let entry_func = join_module.functions.get(&entry_id) + .expect("append_defs_entry function not found"); + assert_eq!(entry_func.name, "append_defs_entry"); + assert_eq!(entry_func.params.len(), 3, "append_defs_entry has 3 parameters (dst, defs_box, n)"); + + // loop_step 関数の検証 + let loop_step_func = join_module.functions.get(&loop_step_id) + .expect("loop_step function not found"); + assert_eq!(loop_step_func.name, "loop_step"); + assert_eq!(loop_step_func.params.len(), 4, "loop_step has 4 parameters (dst, defs_box, n, i)"); + + // ValueId range 検証 (9000-10999) + assert_eq!(entry_func.params[0].0, 9000, "dst parameter should be ValueId(9000)"); + assert_eq!(entry_func.params[1].0, 9001, "defs_box parameter should be ValueId(9001)"); + assert_eq!(entry_func.params[2].0, 9002, "n parameter should be ValueId(9002)"); + + assert_eq!(loop_step_func.params[0].0, 10000, "dst_loop parameter should be ValueId(10000)"); + assert_eq!(loop_step_func.params[1].0, 10001, "defs_box_loop parameter should be ValueId(10001)"); + assert_eq!(loop_step_func.params[2].0, 10002, "n_loop parameter should be ValueId(10002)"); + assert_eq!(loop_step_func.params[3].0, 10003, "i_loop parameter should be ValueId(10003)"); + + eprintln!("[joinir/funcscanner_append_defs] ✅ 自動変換成功(Phase 27.14)"); +} + +#[test] +fn mir_joinir_funcscanner_append_defs_type_sanity() { + // Phase 27.14: 型定義の基本的なサニティチェック(常時実行) + // funcscanner_append_defs 用の JoinFunction が作成できることを確認 + + let entry_id = JoinFuncId::new(30); + let entry_func = JoinFunction::new( + entry_id, + "funcscanner_append_defs_test".to_string(), + vec![ValueId(9000), ValueId(9001), ValueId(9002)], + ); + + assert_eq!(entry_func.id, entry_id); + assert_eq!(entry_func.name, "funcscanner_append_defs_test"); + assert_eq!(entry_func.params.len(), 3); + assert_eq!(entry_func.body.len(), 0); +} + +#[test] +fn mir_joinir_funcscanner_append_defs_empty_module_returns_none() { + // Phase 27.14: 空の MIR モジュールでは None を返すことを確認 + // FuncScannerBox._append_defs/2 関数が存在しない場合のフォールバック動作 + + // 最小限の MIR モジュールを作成 + use crate::mir::MirModule; + let test_module = MirModule::new("test_module".to_string()); + + // 対象関数が存在しないので None が返される(正常なフォールバック) + let result = lower_funcscanner_append_defs_to_joinir(&test_module); + + eprintln!("[joinir/funcscanner_append_defs] empty_module test: result is None (expected)"); + assert!(result.is_none(), "Empty MirModule should return None (target function not found)"); +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 5e5e26c8..ee034d40 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -15,6 +15,7 @@ pub mod mir_joinir_min; // Phase 26-H: JoinIR型定義妥当性確認 pub mod mir_joinir_skip_ws; // Phase 27.0: minimal_ssa_skip_ws JoinIR変換 pub mod mir_joinir_funcscanner_trim; // Phase 27.1: FuncScannerBox.trim JoinIR変換 pub mod mir_joinir_stage1_using_resolver_min; // Phase 27.12: Stage1UsingResolverBox.resolve_for_source JoinIR変換 +pub mod mir_joinir_funcscanner_append_defs; // Phase 27.14: FuncScannerBox._append_defs JoinIR変換 pub mod joinir_runner_min; // Phase 27.2: JoinIR 実行器 A/B 比較テスト pub mod mir_locals_ssa; pub mod mir_loopform_conditional_reassign;