fix(joinir): stabilize Phase 256 merge (jump_args, DCE, func names)
This commit is contained in:
@ -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 の責務を縮退)。
|
||||
|
||||
|
||||
@ -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<VarId>,
|
||||
cond: Option<VarId>,
|
||||
},
|
||||
Call {
|
||||
func: JoinFuncId,
|
||||
args: Vec<VarId>,
|
||||
dst: Option<VarId>,
|
||||
k_next: Option<JoinContId>,
|
||||
},
|
||||
Ret { value: Option<VarId> },
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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<ValueId>,
|
||||
pub host_inputs: Vec<ValueId>,
|
||||
pub loop_invariants: Vec<(String, ValueId)>,
|
||||
pub exit_bindings: Vec<LoopExitBinding>,
|
||||
pub expr_result: Option<ValueId>,
|
||||
pub loop_var_name: Option<String>,
|
||||
pub continuation_func_ids: std::collections::BTreeSet<String>,
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
契約の 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<MirInstruction>,
|
||||
pub instruction_spans: Vec<Span>,
|
||||
pub terminator: Option<MirInstruction>,
|
||||
pub jump_args: Option<Vec<ValueId>>,
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
相談したい:
|
||||
- `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 が崩れる
|
||||
@ -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)では、箱化のための箱化で複雑さが増えやすいので、以下を推奨する:
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<MirInstruction>,
|
||||
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<BTreeMap<JoinFuncId, String>>,
|
||||
}
|
||||
|
||||
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<JoinFuncId, String>) -> 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<ValueId>,
|
||||
) -> 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,
|
||||
|
||||
@ -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<JoinFuncId, String>,
|
||||
) -> Result<MirFunction, JoinIrVmBridgeError> {
|
||||
let entry_block = BasicBlockId(0);
|
||||
|
||||
let param_types = join_func
|
||||
.params
|
||||
.iter()
|
||||
.map(|_| MirType::Unknown)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
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)]
|
||||
|
||||
@ -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<JoinFuncId, String> = 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.
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
Reference in New Issue
Block a user