diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index 9d9e1dd8..2a0ff3f7 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -48,12 +48,14 @@ ## 2025-12-19:Phase 256(StringUtils.split/2 可変 step ループ)🔜 - Phase 256 README: `docs/development/current/main/phases/phase-256/README.md` -- Current first FAIL: `StringUtils.split/2`(`jump_args length mismatch` → `Carrier 'i' has no latch incoming set`) +- Current first FAIL: `StringUtils.split/2`(MIR verification: “Value defined multiple times” + SSA undef) - 状況: - `MirInstruction::Select` の導入は完了、Pattern6(index_of)は PASS 維持。 - `ValueId(57)` undefined は根治(原因は `const_1` 未初期化)。 - SSA undef(`%49/%67`)は P1.7 で根治(continuation 関数名の SSOT 不一致)。 - - 残りは Pattern7 の carrier PHI 配線(ExitLine / jump_args 契約)を直す段階。 + - P1.8で ExitLine/jump_args の余剰許容と関数名マッピングを整流。 + - P1.9で `JoinInst::Jump` を tail call として bridge に落とし、`jump_args` を SSOT として保持。 + - P1.10で DCE が `jump_args` を used 扱いし、`instruction_spans` を同期(SPAN MISMATCH 根治)。 - P1.5-DBG: boundary entry params の契約チェックを追加(VM実行前 fail-fast)。 - P1.6: 契約チェックの薄い集約 `run_all_pipeline_checks()` を導入(pipeline の責務を縮退)。 diff --git a/docs/development/current/main/investigations/phase-256-joinir-contract-questions.md b/docs/development/current/main/investigations/phase-256-joinir-contract-questions.md new file mode 100644 index 00000000..60b77e43 --- /dev/null +++ b/docs/development/current/main/investigations/phase-256-joinir-contract-questions.md @@ -0,0 +1,169 @@ +# Phase 256: JoinIR Contract Questions (for ChatGPT Pro) + +目的: Phase 256 の詰まり(Jump/continuation/params/jump_args の暗黙契約)を、設計として固めるための相談メモ。 + +--- + +## Q1. SSOT をどこに置くべき? + +JoinIR の「意味論 SSOT」をどこに置くべきか。 + +- A) Structured JoinIR を SSOT として維持し、bridge/merge が意味解釈する +- B) Normalized JoinIR を SSOT とし、Structured→Normalized の正規化箱を必須化する + +判断材料として、現在の層の境界と責務: +- Pattern lowerer(Structured JoinIR 生成) +- `join_ir_vm_bridge`(JoinIR→MIR 変換) +- merge(MIR inline + PHI/ExitLine wiring) + +--- + +## Q2. `JoinInst::Jump` の正規形(不変条件) + +現状の詰まりは `Jump` が層を跨ぐときに「tail call 等価」になったり「Return 化」になったりして、continuation が失われる点にある。 + +相談したい: +- `JoinInst::Jump { cont, args, cond }` を SSOT 的にどう定義するべきか? +- cond 付き Jump は JoinIR 語彙として残すべきか?それとも IfMerge に寄せるべきか? + +最小コード(JoinIR 命令): + +```rust +// src/mir/join_ir/mod.rs +pub enum JoinInst { + // ... + Jump { + cont: JoinContId, + args: Vec, + cond: Option, + }, + Call { + func: JoinFuncId, + args: Vec, + dst: Option, + k_next: Option, + }, + Ret { value: Option }, + // ... +} +``` + +--- + +## Q3. boundary/params/jump_args の順序契約をどこで固定する? + +Phase 256 では、次の対応関係が暗黙で、崩れると SSA undef / PHI wiring fail-fast になりやすい。 + +- `JoinInlineBoundary.join_inputs` ↔ `JoinModule.entry.params` +- `exit_bindings` ↔ ExitLine の carrier PHI reconnect +- `jump_args`(tail call args metadata)↔ ExitLine の latch incoming 復元 + +最小コード(boundary): + +```rust +// src/mir/join_ir/lowering/inline_boundary.rs +pub struct JoinInlineBoundary { + pub join_inputs: Vec, + pub host_inputs: Vec, + pub loop_invariants: Vec<(String, ValueId)>, + pub exit_bindings: Vec, + pub expr_result: Option, + pub loop_var_name: Option, + pub continuation_func_ids: std::collections::BTreeSet, + // ... +} +``` + +契約の fail-fast は現在ここで行っている: + +```rust +// src/mir/builder/control_flow/joinir/merge/contract_checks.rs +pub(in crate::mir::builder::control_flow::joinir) fn run_all_pipeline_checks( + join_module: &crate::mir::join_ir::JoinModule, + boundary: &JoinInlineBoundary, +) -> Result<(), String> { + verify_boundary_entry_params(join_module, boundary)?; + // ... + Ok(()) +} +``` + +相談したい: +- この順序契約は「boundary」「normalizer」「bridge」「merge」のどの層が SSOT になるべきか? +- fail-fast の責務をどこに置くべきか(今は conversion_pipeline 直前)? + +--- + +## Q4. continuation の識別: `JoinFuncId` vs `String`(関数名) + +Phase 256 P1.7 で「continuation 関数が merge で見つからず SSA undef」になった。 +原因は bridge 側と merge 側の “関数名 SSOT” 不一致。 + +現状の暫定 SSOT は `canonical_names`: + +```rust +// src/mir/join_ir/lowering/canonical_names.rs +pub const K_EXIT: &str = "k_exit"; +pub const K_EXIT_LEGACY: &str = "join_func_2"; +pub const LOOP_STEP: &str = "loop_step"; +pub const MAIN: &str = "main"; +``` + +相談したい: +- continuation を `JoinFuncId` で保持し、bridge で 1 回だけ名前解決するべきか? +- それとも `String` を SSOT にして “MirModule key” と一致させ続けるべきか? +- 併存するなら、変換境界(片方→片方)をどこに置くべきか? + +--- + +## Q5. 正規化 shadow(`join_func_N`)との共存戦略 + +normalized_shadow 側は `join_func_2` のような命名を使う箇所がある。 +この legacy をいつ・どう統一するべきか(または統一しないなら境界をどう明文化するか)。 + +--- + +## Q6. `jump_args` は MIR のどの層の SSOT か? + +観測: +- ExitLine/merge 側は `BasicBlock.jump_args` を「exit/carry 値の SSOT」として参照する。 +- bridge 側で tail call / Jump を生成するときに `jump_args` を落とし忘れると、ExitLine が fallback 経路へ入りやすく、 + SSA/dominance の破綻につながる。 + +最小コード(MIR basic block): + +```rust +// src/mir/mod.rs +pub struct BasicBlock { + pub instructions: Vec, + pub instruction_spans: Vec, + pub terminator: Option, + pub jump_args: Option>, + // ... +} +``` + +相談したい: +- `jump_args` は terminator(Jump/Branch/Return/Call)に埋め込むべきか、それとも BasicBlock の外部メタのままでよいか? +- `jump_args` の契約(順序・長さ・expr_result の有無・invariants の混在)をどこで固定するべきか? + +--- + +## Q7. Optimizer/DCE の不変条件(spans 同期と jump_args) + +観測: +- DCE が `jump_args` だけで使われる値を “unused” とみなすと、Copy/Const が消えて merge が壊れる。 +- DCE が `instructions` だけを削って `instruction_spans` を同期しないと、SPAN MISMATCH が発生しデバッグが困難になる。 + +相談したい: +- `jump_args` は “use” として扱うのが SSOT として正しいか? +- spans 同期は「各パスの責務」か、それとも `BasicBlock` の API(例: spanned filter)に閉じ込めるべきか? + +--- + +## 観測された失敗例(短く) + +- SSA undef(関数名不一致で continuation が merge 対象にならず到達不能/未定義が露出) +- ExitLine: `jump_args` の長さ契約ミスマッチ(carriers と invariants の混在) +- `JoinInst::Jump` が bridge で “Return 化” され、continuation の意味が落ちる疑い + - DCE が `jump_args` 由来の use を落とし、Copy が消えて SSA/dominance が崩れる diff --git a/docs/development/current/main/phases/phase-256/README.md b/docs/development/current/main/phases/phase-256/README.md index 7e1cf030..efb1021c 100644 --- a/docs/development/current/main/phases/phase-256/README.md +++ b/docs/development/current/main/phases/phase-256/README.md @@ -8,12 +8,13 @@ Related: ## Current Status (SSOT) -- Current first FAIL: `StringUtils.split/2`(`jump_args length mismatch` → `Carrier 'i' has no latch incoming set`) +- Current first FAIL: `StringUtils.split/2`(MIR verification: “Value defined multiple times” + SSA undef) - Pattern6 は PASS 維持 - 直近の完了: + - P1.10: DCE が `jump_args` 参照を保持し、`instruction_spans` と同期するよう修正(回帰テスト追加) - P1.7: SSA undef(`%49/%67`)根治(continuation 関数名の SSOT 不一致) - P1.6: pipeline contract checks を `run_all_pipeline_checks()` に集約 -- 次の作業: P1.8(Pattern7 の carrier PHI wiring / ExitLine + jump_args 契約の修正) +- 次の作業: P1.11(merge 側の ExitLine/PHI/dominance を `--verify` で緑に戻す) --- @@ -384,6 +385,35 @@ Option A(Pattern 7 新設)を推奨。 結果: - `./target/release/hakorune --backend vm --verify apps/tests/phase256_p0_split_min.hako` で SSA undef は消滅 +--- + +## 進捗(P1.8) + +### P1.8: ExitLine/jump_args と関数名マッピング整流(完了) + +変更(要旨): +- ExitArgsCollector 側で「余剰 jump_args(invariants)」を許容し、`expected 3 or 4 but got 5` を解消 +- JoinIR→MIR bridge 側で “join_func_N” 由来の名前と “JoinFunction.name” の不一致を解消するため、関数名マッピングを導入/伝播 + +結果: +- 旧 first FAIL(jump_args length mismatch)は解消 + +### P1.9: Jump を tail call として表現(完了) + +変更(要旨): +- JoinIR→MIR bridge で `JoinInst::Jump` を “continuation への tail call” として落とす +- `BasicBlock.jump_args` を tail call と同様に SSOT として保持(ExitLine/collector の復元入力) + +結果: +- `JoinInst::Jump` が “ret args[0]” 相当になり continuation が失われる問題は解消 + +### P1.10: DCE の jump_args + spans 同期(完了) + +変更(要旨): +- DCE が `jump_args` で使われる値を used として扱い、純命令の除去で Copy が消えないようにする +- `instruction_spans` と `instructions` の同期不変条件を維持(SPAN MISMATCH 根治) +- 回帰テストを追加(`test_dce_keeps_jump_args_values`, `test_dce_syncs_instruction_spans`) + ### リファクタリング方針(P1.6候補 / 先送り推奨) 現時点(split がまだ FAIL)では、箱化のための箱化で複雑さが増えやすいので、以下を推奨する: diff --git a/src/mir/builder/control_flow/joinir/merge/exit_args_collector.rs b/src/mir/builder/control_flow/joinir/merge/exit_args_collector.rs index 0ed6a029..a6cdbdf6 100644 --- a/src/mir/builder/control_flow/joinir/merge/exit_args_collector.rs +++ b/src/mir/builder/control_flow/joinir/merge/exit_args_collector.rs @@ -188,20 +188,15 @@ impl ExitArgsCollectorBox { Ok(0) // Best effort: try direct mapping } } else { - // Too long - unexpected extra args - let msg = format!( - "[joinir/exit-line] jump_args length mismatch: expected {} or {} (exit_bindings carriers ±1) but got {} in block {:?}", - exit_phi_bindings_len, - exit_phi_bindings_len + 1, - jump_args_len, - block_id + // Too long - extra args beyond carriers (e.g., invariants in Pattern 7) + // Phase 256 P1.8: Allow excess args as long as we have enough for carriers + // Direct mapping: jump_args[0..N] = exit_phi_bindings[0..N], rest ignored + #[cfg(debug_assertions)] + eprintln!( + "[joinir/exit-line] jump_args has {} extra args (block {:?}), ignoring invariants", + jump_args_len - exit_phi_bindings_len, block_id ); - if strict_exit { - Err(msg) - } else { - eprintln!("[DEBUG-177] {}", msg); - Ok(0) // Best effort: try direct mapping - } + Ok(0) // Direct mapping: first N args are carriers } } 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 f9f63b2a..12bd544c 100644 --- a/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs +++ b/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs @@ -1162,15 +1162,44 @@ pub(super) fn merge_and_rewrite( // Choosing `.iter().next()` can therefore pick the wrong function and skip // host→JoinIR Copy injection. Instead, pick the function whose params match // the boundary.join_inputs (the entry env params). - let (entry_func_name, entry_func) = mir_module - .functions - .iter() - .find(|(_, func)| func.params == boundary.join_inputs) - .or_else(|| mir_module.functions.iter().next()) - .ok_or("JoinIR module has no functions")?; + // + // Phase 256 P1.10.1: Prefer "main" if its params match the boundary join_inputs. + let (entry_func_name, entry_func) = { + use crate::mir::join_ir::lowering::canonical_names as cn; + if let Some(main) = mir_module.functions.get(cn::MAIN) { + if main.params == boundary.join_inputs { + (cn::MAIN, main) + } else { + mir_module + .functions + .iter() + .find(|(_, func)| func.params == boundary.join_inputs) + .or_else(|| mir_module.functions.iter().next()) + .map(|(name, func)| (name.as_str(), func)) + .ok_or("JoinIR module has no functions")? + } + } else { + mir_module + .functions + .iter() + .find(|(_, func)| func.params == boundary.join_inputs) + .or_else(|| mir_module.functions.iter().next()) + .map(|(name, func)| (name.as_str(), func)) + .ok_or("JoinIR module has no functions")? + } + }; let entry_block_remapped = remapper .get_block(entry_func_name, entry_func.entry_block) .ok_or_else(|| format!("Entry block not found for {}", entry_func_name))?; + log!( + true, + "[cf_loop/joinir] Phase 256 P1.10.1: Boundary entry selection: func='{}' entry_block={:?} remapped={:?} join_inputs={:?} entry_params={:?}", + entry_func_name, + entry_func.entry_block, + entry_block_remapped, + boundary.join_inputs, + entry_func.params + ); // Create BTreeMap from remapper for BoundaryInjector (temporary adapter) // Phase 222.5-E: HashMap → BTreeMap for determinism diff --git a/src/mir/join_ir_vm_bridge/joinir_block_converter.rs b/src/mir/join_ir_vm_bridge/joinir_block_converter.rs index f5f22d99..2cec3283 100644 --- a/src/mir/join_ir_vm_bridge/joinir_block_converter.rs +++ b/src/mir/join_ir_vm_bridge/joinir_block_converter.rs @@ -6,9 +6,10 @@ //! - ブロックID マッピング管理 use crate::ast::Span; -use crate::mir::join_ir::{JoinInst, MirLikeInst}; +use crate::mir::join_ir::{JoinFuncId, JoinInst, MirLikeInst}; use crate::mir::{BasicBlockId, EffectMask, MirFunction, MirInstruction, MirType, ValueId}; use crate::mir::types::ConstValue; +use std::collections::BTreeMap; use super::{convert_mir_like_inst, join_func_name, JoinIrVmBridgeError}; @@ -22,6 +23,9 @@ pub struct JoinIrBlockConverter { current_block_id: BasicBlockId, current_instructions: Vec, next_block_id: u32, + /// Phase 256 P1.8: Map from JoinFuncId to actual function name + /// When set, handle_call uses this instead of join_func_name() + func_name_map: Option>, } impl JoinIrBlockConverter { @@ -30,6 +34,18 @@ impl JoinIrBlockConverter { current_block_id: BasicBlockId(0), // entry block current_instructions: Vec::new(), next_block_id: 1, // start from 1 (0 is entry) + func_name_map: None, + } + } + + /// Phase 256 P1.8: Create converter with function name map + /// This ensures Call instructions use actual function names instead of "join_func_N" + pub fn new_with_func_names(func_name_map: BTreeMap) -> Self { + Self { + current_block_id: BasicBlockId(0), + current_instructions: Vec::new(), + next_block_id: 1, + func_name_map: Some(func_name_map), } } @@ -349,7 +365,12 @@ impl JoinIrBlockConverter { )); } - let func_name = join_func_name(*func); + // Phase 256 P1.8: Use actual function name if available + let func_name = if let Some(ref map) = self.func_name_map { + map.get(func).cloned().unwrap_or_else(|| join_func_name(*func)) + } else { + join_func_name(*func) + }; // Phase 131 P2: Stable function name ValueId (module-global SSOT) // @@ -420,21 +441,31 @@ impl JoinIrBlockConverter { fn handle_jump( &mut self, mir_func: &mut MirFunction, - _cont: &crate::mir::join_ir::JoinContId, + cont: &crate::mir::join_ir::JoinContId, args: &[ValueId], cond: &Option, ) -> Result<(), JoinIrVmBridgeError> { - // Phase 246-EX: Preserve ALL Jump args as metadata for exit PHI construction - // Phase 27-shortterm S-4.4-A: Jump → Branch/Return + // Phase 256 P1.9: Jump → tail call to continuation function + // Previously was just `ret args[0]`, now generates `call cont(args...); ret result` debug_log!( - "[joinir_block] Converting Jump args={:?}, cond={:?}", + "[joinir_block] Converting Jump to tail call: cont={:?}, args={:?}, cond={:?}", + cont, args, cond ); + // Get continuation function name + let cont_name = self.get_continuation_name(cont); + + // Phase 256 P1.9: Use distinct ValueIds for Jump tail call + // FUNC_NAME_ID_BASE for call targets, 99992 for Jump result (distinct from 99991 in handle_call) + const JUMP_FUNC_NAME_ID_BASE: u32 = 91000; // Different from handle_call's 90000 + let func_name_id = ValueId(JUMP_FUNC_NAME_ID_BASE + cont.0); + let call_result_id = ValueId(99992); // Distinct from handle_call's 99991 + match cond { Some(cond_var) => { - // Conditional jump + // Conditional jump → Branch + tail call to continuation let exit_block_id = BasicBlockId(self.next_block_id); self.next_block_id += 1; let continue_block_id = BasicBlockId(self.next_block_id); @@ -453,16 +484,27 @@ impl JoinIrBlockConverter { branch_terminator, ); - // Phase 246-EX: Store all Jump args in exit block metadata - // Exit block: Create with all Jump args stored as metadata - let exit_value = args.first().copied(); + // Exit block: tail call to continuation function let mut exit_block = crate::mir::BasicBlock::new(exit_block_id); - // Phase 246-EX: Store Jump args in a new metadata field - // This preserves carrier values for exit PHI construction + // Phase 246-EX: Store Jump args in metadata for exit PHI construction exit_block.jump_args = Some(args.to_vec()); - exit_block.terminator = Some(MirInstruction::Return { value: exit_value }); + // Phase 256 P1.9: Generate tail call to continuation + exit_block.instructions.push(MirInstruction::Const { + dst: func_name_id, + value: crate::mir::ConstValue::String(cont_name.clone()), + }); + exit_block.instruction_spans.push(Span::unknown()); + exit_block.instructions.push(MirInstruction::Call { + dst: Some(call_result_id), + func: func_name_id, + callee: None, + args: args.to_vec(), + effects: EffectMask::PURE, + }); + exit_block.instruction_spans.push(Span::unknown()); + exit_block.terminator = Some(MirInstruction::Return { value: Some(call_result_id) }); mir_func.blocks.insert(exit_block_id, exit_block); // Continue block @@ -472,9 +514,28 @@ impl JoinIrBlockConverter { self.current_block_id = continue_block_id; } None => { - // Unconditional jump - let exit_value = args.first().copied(); - let return_terminator = MirInstruction::Return { value: exit_value }; + // Unconditional jump → tail call to continuation + // Finalize current block with tail call + self.current_instructions.push(MirInstruction::Const { + dst: func_name_id, + value: crate::mir::ConstValue::String(cont_name), + }); + self.current_instructions.push(MirInstruction::Call { + dst: Some(call_result_id), + func: func_name_id, + callee: None, + args: args.to_vec(), + effects: EffectMask::PURE, + }); + + // Preserve jump args as metadata (SSOT for ExitLine/jump_args wiring). + if let Some(block) = mir_func.blocks.get_mut(&self.current_block_id) { + if block.jump_args.is_none() { + block.jump_args = Some(args.to_vec()); + } + } + + let return_terminator = MirInstruction::Return { value: Some(call_result_id) }; Self::finalize_block( mir_func, @@ -487,6 +548,18 @@ impl JoinIrBlockConverter { Ok(()) } + /// Phase 256 P1.9: Get continuation function name from func_name_map + fn get_continuation_name(&self, cont: &crate::mir::join_ir::JoinContId) -> String { + // JoinContId.0 == JoinFuncId.0 (same underlying ID via as_cont()) + if let Some(ref map) = self.func_name_map { + if let Some(name) = map.get(&JoinFuncId(cont.0)) { + return name.clone(); + } + } + // Fallback: use join_func_name() + join_func_name(JoinFuncId(cont.0)) + } + fn handle_select( &mut self, mir_func: &mut MirFunction, diff --git a/src/mir/join_ir_vm_bridge/joinir_function_converter.rs b/src/mir/join_ir_vm_bridge/joinir_function_converter.rs index 6408a0c6..b9b63c2d 100644 --- a/src/mir/join_ir_vm_bridge/joinir_function_converter.rs +++ b/src/mir/join_ir_vm_bridge/joinir_function_converter.rs @@ -5,8 +5,9 @@ //! - ブロック変換の統合 //! - 関数署名の管理 -use crate::mir::join_ir::{JoinFunction, JoinModule}; +use crate::mir::join_ir::{JoinFuncId, JoinFunction, JoinModule}; use crate::mir::{BasicBlockId, EffectMask, FunctionSignature, MirFunction, MirModule, MirType}; +use std::collections::BTreeMap; use super::join_func_name; use super::joinir_block_converter::JoinIrBlockConverter; @@ -116,6 +117,53 @@ impl JoinIrFunctionConverter { Ok(mir_func) } + + /// Phase 256 P1.8: Convert function with actual function name map + /// + /// This variant ensures Call instructions use actual function names ("main", "loop_step", "k_exit") + /// instead of generated names ("join_func_0", "join_func_1", etc.) + pub(crate) fn convert_function_with_func_names( + join_func: &JoinFunction, + func_name_map: BTreeMap, + ) -> Result { + let entry_block = BasicBlockId(0); + + let param_types = join_func + .params + .iter() + .map(|_| MirType::Unknown) + .collect::>(); + + let signature = FunctionSignature { + name: join_func.name.clone(), + params: param_types, + return_type: MirType::Unknown, + effects: EffectMask::PURE, + }; + + let mut mir_func = MirFunction::new(signature, entry_block); + mir_func.params = join_func.params.clone(); + + // Phase 256 P1.8: Use BlockConverter with function name map + let mut block_converter = JoinIrBlockConverter::new_with_func_names(func_name_map); + block_converter.convert_function_body(&mut mir_func, &join_func.body)?; + + debug_log!( + "[joinir_vm_bridge] Function '{}' has {} blocks:", + mir_func.signature.name, + mir_func.blocks.len() + ); + for (block_id, block) in &mir_func.blocks { + debug_log!( + " Block {:?}: {} instructions, terminator={:?}", + block_id, + block.instructions.len(), + block.terminator + ); + } + + Ok(mir_func) + } } #[cfg(test)] diff --git a/src/mir/join_ir_vm_bridge/meta.rs b/src/mir/join_ir_vm_bridge/meta.rs index ec1f7043..3de197e2 100644 --- a/src/mir/join_ir_vm_bridge/meta.rs +++ b/src/mir/join_ir_vm_bridge/meta.rs @@ -1,8 +1,9 @@ // Phase 190: Use modularized converter use super::{JoinIrFunctionConverter, JoinIrVmBridgeError}; use crate::mir::join_ir::frontend::JoinFuncMetaMap; -use crate::mir::join_ir::JoinModule; +use crate::mir::join_ir::{JoinFuncId, JoinModule}; use crate::mir::{MirFunction, MirModule}; +use std::collections::BTreeMap; /// Phase 40-1実験用: JoinFuncMetaを使ったMIR変換 /// @@ -29,6 +30,15 @@ pub fn convert_join_module_to_mir_with_meta( let mut mir_module = MirModule::new("joinir_bridge_with_meta".to_string()); + // Phase 256 P1.8: Build function name map for all functions in the module + // This ensures Call instructions use actual names ("main", "loop_step", "k_exit") + // instead of generated names ("join_func_0", "join_func_1", etc.) + let func_name_map: BTreeMap = module + .functions + .iter() + .map(|(id, func)| (*id, func.name.clone())) + .collect(); + // 1. 各関数を変換 for (func_id, join_func) in &module.functions { debug_log!( @@ -37,8 +47,11 @@ pub fn convert_join_module_to_mir_with_meta( join_func.name ); - // 2. 基本のMIR変換(Phase 190: modularized converter) - let mir_func = JoinIrFunctionConverter::convert_function(join_func)?; + // 2. 基本のMIR変換(Phase 256 P1.8: with func_name_map) + let mir_func = JoinIrFunctionConverter::convert_function_with_func_names( + join_func, + func_name_map.clone(), + )?; // Phase 189 DEBUG: Dump MirFunction blocks to check PHI presence // Guarded to avoid polluting stdout/stderr in normal runs. diff --git a/src/mir/loop_canonicalizer/canonicalizer_tests.rs b/src/mir/loop_canonicalizer/canonicalizer_tests.rs index a7c34cda..0c191c3c 100644 --- a/src/mir/loop_canonicalizer/canonicalizer_tests.rs +++ b/src/mir/loop_canonicalizer/canonicalizer_tests.rs @@ -3,8 +3,9 @@ //! Separated from canonicalizer.rs for better maintainability. use super::canonicalizer::canonicalize_loop_expr; -use super::skeleton_types::{CarrierRole, LoopPatternKind, SkeletonStep, UpdateKind}; use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span}; +use crate::mir::loop_pattern_detection::LoopPatternKind; +use super::skeleton_types::{CarrierRole, SkeletonStep, UpdateKind}; #[test] fn test_canonicalize_rejects_non_loop() { diff --git a/src/mir/optimizer.rs b/src/mir/optimizer.rs index 373fe49d..92ae72a6 100644 --- a/src/mir/optimizer.rs +++ b/src/mir/optimizer.rs @@ -522,4 +522,80 @@ mod tests { "TypeOp should not be dropped by DCE when used by console.log (ExternCall)" ); } + + #[test] + fn test_dce_keeps_jump_args_values() { + let signature = FunctionSignature { + name: "main".to_string(), + params: vec![], + return_type: MirType::Void, + effects: super::super::effect::EffectMask::PURE, + }; + let mut func = MirFunction::new(signature, BasicBlockId::new(0)); + let bb0 = BasicBlockId::new(0); + let mut b0 = BasicBlock::new(bb0); + let v0 = ValueId::new(0); + let v1 = ValueId::new(1); + b0.add_instruction(MirInstruction::Const { + dst: v0, + value: ConstValue::Integer(1), + }); + b0.add_instruction(MirInstruction::Copy { dst: v1, src: v0 }); + b0.add_instruction(MirInstruction::Return { value: None }); + b0.jump_args = Some(vec![v1]); + func.add_block(b0); + let mut module = MirModule::new("test".to_string()); + module.add_function(func); + + crate::mir::passes::dce::eliminate_dead_code(&mut module); + + let f = module.get_function("main").unwrap(); + let block = f.get_block(bb0).unwrap(); + let has_copy = block + .instructions + .iter() + .any(|inst| matches!(inst, MirInstruction::Copy { .. })); + assert!(has_copy, "Copy used only by jump_args should not be eliminated"); + } + + #[test] + fn test_dce_syncs_instruction_spans() { + let signature = FunctionSignature { + name: "main".to_string(), + params: vec![], + return_type: MirType::Integer, + effects: super::super::effect::EffectMask::PURE, + }; + let mut func = MirFunction::new(signature, BasicBlockId::new(0)); + let bb0 = BasicBlockId::new(0); + let mut b0 = BasicBlock::new(bb0); + let v0 = ValueId::new(0); + let v1 = ValueId::new(1); + b0.add_instruction(MirInstruction::Const { + dst: v0, + value: ConstValue::Integer(1), + }); + b0.add_instruction(MirInstruction::Const { + dst: v1, + value: ConstValue::Integer(2), + }); + b0.add_instruction(MirInstruction::Return { value: Some(v0) }); + func.add_block(b0); + let mut module = MirModule::new("test".to_string()); + module.add_function(func); + + crate::mir::passes::dce::eliminate_dead_code(&mut module); + + let f = module.get_function("main").unwrap(); + let block = f.get_block(bb0).unwrap(); + assert_eq!( + block.instructions.len(), + block.instruction_spans.len(), + "Instruction spans must stay aligned after DCE" + ); + let has_unused_const = block.instructions.iter().any(|inst| { + matches!(inst, MirInstruction::Const { dst, .. } if *dst == v1) + }); + assert!(!has_unused_const, "Unused const should be eliminated by DCE"); + } } diff --git a/src/mir/passes/dce.rs b/src/mir/passes/dce.rs index e630749d..0125e4e9 100644 --- a/src/mir/passes/dce.rs +++ b/src/mir/passes/dce.rs @@ -36,6 +36,11 @@ fn eliminate_dead_code_in_function(function: &mut MirFunction) -> usize { used_values.insert(u); } } + if let Some(args) = &block.jump_args { + for &u in args { + used_values.insert(u); + } + } } // Backward propagation: if a value is used, mark its operands as used @@ -61,12 +66,15 @@ fn eliminate_dead_code_in_function(function: &mut MirFunction) -> usize { let mut eliminated = 0usize; let dce_trace = std::env::var("NYASH_DCE_TRACE").ok().as_deref() == Some("1"); for (bbid, block) in &mut function.blocks { - block.instructions.retain(|inst| { + let insts = std::mem::take(&mut block.instructions); + let spans = std::mem::take(&mut block.instruction_spans); + let mut kept_insts = Vec::with_capacity(insts.len()); + let mut kept_spans = Vec::with_capacity(spans.len()); + for (inst, span) in insts.into_iter().zip(spans.into_iter()) { + let mut keep = true; if inst.effects().is_pure() { if let Some(dst) = inst.dst_value() { if !used_values.contains(&dst) { - // Keep indices stable is not required here; remove entirely - // NYASH_DCE_TRACE=1 enables logging for debugging DCE issues if dce_trace { eprintln!( "[dce] Eliminating unused pure instruction in bb{}: %{} = {:?}", @@ -74,12 +82,17 @@ fn eliminate_dead_code_in_function(function: &mut MirFunction) -> usize { ); } eliminated += 1; - return false; + keep = false; } } } - true - }); + if keep { + kept_insts.push(inst); + kept_spans.push(span); + } + } + block.instructions = kept_insts; + block.instruction_spans = kept_spans; } if eliminated > 0 { function.update_cfg();