docs(joinir): Phase 187 LoopBuilder Physical Removal - Completion Documentation
## Changes - Added Phase 187 section to Phase 180 README documenting complete LoopBuilder deletion - Documented all 4 tasks completion (187-1 through 187-4) - Added architectural impact analysis before/after Phase 187 - Documented code deletion summary and archival strategy - Explained NYASH_LEGACY_LOOPBUILDER variable status after deletion ## Key Achievements - LoopBuilder module (8 files, ~1000+ lines) completely deleted - IfInLoopPhiEmitter preserved in minimal new module - JoinIR Frontend is now sole authoritative loop lowering system - Explicit failures replace implicit fallbacks ## Architectural Impact - Single authoritative path (JoinIR Frontend) - No implicit fallbacks - Future JoinIR expansion is only way forward - Fail-Fast principle enforced at architecture level ## Related Phases - Phase 185: Strict mode semantics unified - Phase 186: Hard freeze with access control guard - Phase 187: Physical module deletion (this change) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -1,65 +1,51 @@
|
|||||||
# Current Task
|
# Current Task
|
||||||
|
|
||||||
## 🚧 Phase 186: LoopBuilder Hard Freeze (In Progress)
|
## ✅ Phase 186: LoopBuilder Hard Freeze (Completed - 2025-12-04)
|
||||||
|
|
||||||
**Status**: 🟡 Design & Investigation Done → Code change pending
|
**Status**: ✅ **All Tasks Completed** → Phase 187 (Physical Removal) Ready
|
||||||
|
|
||||||
**Key Finding**: `cf_loop()` からの LoopBuilder 呼び出しにガードが無く、JoinIR にマッチしない全ループが自動的に LoopBuilder にフォールバックしている。JoinIR mainline の前提が満たされていない。
|
**Key Achievement**: LoopBuilder は `NYASH_LEGACY_LOOPBUILDER=1` の明示 opt-in がない限り起動しない。代表パスは JoinIR-only が既定となり、silent fallback が排除された。
|
||||||
|
|
||||||
### What Phase 186 Discovered
|
### What Was Fixed
|
||||||
|
|
||||||
- File: `src/mir/builder/control_flow.rs:45-62`
|
- **Guard insertion at single instantiation point**
|
||||||
- `try_cf_loop_joinir(..)` で JoinIR を試した後、**環境変数チェック無しで**:
|
- File: `src/mir/builder/control_flow.rs:45-68`
|
||||||
```rust
|
- Behavior:
|
||||||
let mut loop_builder = crate::mir::loop_builder::LoopBuilder::new(self);
|
- JoinIR を試して失敗 → 直ちに `legacy_loopbuilder_enabled()` をチェック。
|
||||||
loop_builder.build_loop(condition, body)
|
- `NYASH_LEGACY_LOOPBUILDER` が無効なら、明示的なエラーを返して停止。
|
||||||
```
|
- 有効なときだけ `LoopBuilder::new(self).build_loop(..)` を呼び出す。
|
||||||
に落ちる。
|
|
||||||
- Result:
|
|
||||||
- JoinIR ルートは `print_tokens/0` と `ArrayExtBox.filter/2` などごく一部だけ。
|
|
||||||
- それ以外のループはすべて LoopBuilder で lowering される。
|
|
||||||
- `NYASH_LEGACY_LOOPBUILDER` が存在するのに、一切参照されていない。
|
|
||||||
|
|
||||||
### Required Fix (for Claude Code)
|
- **NYASH_LEGACY_LOOPBUILDER semantics**
|
||||||
|
- Default: OFF(未設定 or 0)
|
||||||
|
- JoinIR 非対応ループ → エラー(Fail-Fast)
|
||||||
|
- 代表パスは JoinIR-only でテストされる。
|
||||||
|
- ON(=1):
|
||||||
|
- JoinIR 非対応パターンのみ legacy LoopBuilder にフォールバック(dev/test 用)。
|
||||||
|
- Design: Dev 専用、一時的トグル(Phase 187 で除去予定)。
|
||||||
|
|
||||||
**Goal**: LoopBuilder は `NYASH_LEGACY_LOOPBUILDER=1` のときだけ有効。既定は JoinIR-only。
|
### Verification (Representative Paths)
|
||||||
|
|
||||||
- Location: `src/mir/builder/control_flow.rs`(`cf_loop` の JoinIR fallback 部分)
|
- JoinIR-only(`NYASH_LEGACY_LOOPBUILDER=0` or unset):
|
||||||
- Desired pattern:
|
- selfhost 代表ループ: ✅ JoinIR で PASS
|
||||||
```rust
|
- If lowering テスト: ✅ JoinIR 経路のみで PASS(Phase 184/185 済み)
|
||||||
// Phase 186: Add access control guard
|
- Legacy mode(`NYASH_LEGACY_LOOPBUILDER=1`):
|
||||||
if !crate::config::env::joinir_dev::legacy_loopbuilder_enabled() {
|
- JoinIR 非対応パターン: ✅ LoopBuilder fallback により実行可能(dev モードのみ)
|
||||||
return Err(format!(
|
|
||||||
"Loop lowering failed: JoinIR does not support this loop pattern, and LoopBuilder is disabled.\n\
|
|
||||||
Function: {}\n\
|
|
||||||
Hint: Set NYASH_LEGACY_LOOPBUILDER=1 to enable legacy path (dev-only), \
|
|
||||||
or implement JoinIR support for this function.",
|
|
||||||
self.current_function
|
|
||||||
.as_ref()
|
|
||||||
.map(|f| f.signature.name.as_str())
|
|
||||||
.unwrap_or("<unknown>")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let mut loop_builder = crate::mir::loop_builder::LoopBuilder::new(self);
|
|
||||||
loop_builder.build_loop(condition, body)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 186 Tasks
|
### Documentation
|
||||||
|
|
||||||
1. **Guard insertion**
|
- `docs/private/roadmap2/phases/phase-180-joinir-unification-before-selfhost/README.md`
|
||||||
- Add `legacy_loopbuilder_enabled()` guard before LoopBuilder instantiation in `cf_loop`.
|
- 新たに「6. Phase 186: LoopBuilder Hard Freeze」を追加。
|
||||||
- Ensure error message is actionable (function名と env hint を含む)。
|
- アクセス制御ポリシー/検証結果/アーキテクチャ影響を詳細に記録。
|
||||||
|
- `docs/private/roadmap2/phases/phase-186-loopbuilder-freeze/README.md`
|
||||||
|
- Status を Completed に更新。
|
||||||
|
- 初期の「問題報告」と、修正後の制御フローを両方残し、経緯を参照できるようにした。
|
||||||
|
|
||||||
2. **Representative path re-check**
|
### Next Step: Phase 187
|
||||||
- With `NYASH_LEGACY_LOOPBUILDER` unset/0:
|
|
||||||
- `loop_min_while.hako` 他 Phase 181 の代表 loop テストが JoinIR-only で通ること。
|
|
||||||
- With `NYASH_LEGACY_LOOPBUILDER=1`:
|
|
||||||
- 未対応パターンがあっても LoopBuilder fallback で動くこと。
|
|
||||||
|
|
||||||
3. **Docs update**
|
- 物理削除フェーズ(LoopBuilder モジュール削除)の entry 条件が揃った:
|
||||||
- `phase-186-loopbuilder-freeze/README.md` に「ガード挿入済み」を追記(完了後)。
|
- 代表パスは JoinIR-only でグリーン。
|
||||||
- `phase-186-loopbuilder-freeze/test-results.md` に再検証結果を追加。
|
- LoopBuilder は明示 opt-in がない限り決して呼ばれない。
|
||||||
- 本ファイルの Phase 186 セクションを Completed に更新。
|
- docs 上も LoopBuilder = legacy/dev-only と明記済み。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -57,19 +57,14 @@ impl super::MirBuilder {
|
|||||||
eprintln!("[cf_loop] Current stack (simulated): check build_statement vs build_expression_impl");
|
eprintln!("[cf_loop] Current stack (simulated): check build_statement vs build_expression_impl");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 186: LoopBuilder Hard Freeze - Require explicit legacy mode opt-in
|
// Phase 186: LoopBuilder Hard Freeze - Legacy path disabled
|
||||||
if !crate::config::env::joinir_dev::legacy_loopbuilder_enabled() {
|
// Phase 187-2: LoopBuilder module removed - all loops must use JoinIR
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"[joinir/freeze] Loop lowering failed: JoinIR does not support this pattern, and LoopBuilder is disabled.\n\
|
"[joinir/freeze] Loop lowering failed: JoinIR does not support this pattern, and LoopBuilder has been removed.\n\
|
||||||
Function: {}\n\
|
Function: {}\n\
|
||||||
Hint: Set NYASH_LEGACY_LOOPBUILDER=1 to enable legacy path (dev-only)",
|
Hint: This loop pattern is not supported. All loops must use JoinIR lowering.",
|
||||||
self.current_function.as_ref().map(|f| f.signature.name.as_str()).unwrap_or("<unknown>")
|
self.current_function.as_ref().map(|f| f.signature.name.as_str()).unwrap_or("<unknown>")
|
||||||
));
|
));
|
||||||
}
|
|
||||||
|
|
||||||
// Delegate to LoopBuilder for consistent handling (legacy path only)
|
|
||||||
let mut loop_builder = crate::mir::loop_builder::LoopBuilder::new(self);
|
|
||||||
loop_builder.build_loop(condition, body)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Phase 49: Try JoinIR Frontend for mainline integration
|
/// Phase 49: Try JoinIR Frontend for mainline integration
|
||||||
|
|||||||
@ -280,7 +280,7 @@ impl MirBuilder {
|
|||||||
// PHI 生成
|
// PHI 生成
|
||||||
let phi_count = {
|
let phi_count = {
|
||||||
let mut ops = ToplevelOps(self);
|
let mut ops = ToplevelOps(self);
|
||||||
crate::mir::loop_builder::IfInLoopPhiEmitter::emit_toplevel_phis(
|
crate::mir::if_in_loop_phi::IfInLoopPhiEmitter::emit_toplevel_phis(
|
||||||
&phi_spec,
|
&phi_spec,
|
||||||
&pre_if_var_map,
|
&pre_if_var_map,
|
||||||
&then_var_map_end,
|
&then_var_map_end,
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
# loop_builder
|
|
||||||
|
|
||||||
SSA loop lowering for LoopForm v2. This module owns the block layout (preheader/header/body/latch/continue_merge/exit) and delegates PHI construction to `phi_core`.
|
|
||||||
|
|
||||||
## Boundaries
|
|
||||||
- Handles loop CFG + variable snapshots only; no name解決やコード生成 beyond MIR emission.
|
|
||||||
- Uses `phi_core` boxes for PHI wiring; avoid duplicating PHI logic here.
|
|
||||||
- Debug/experimental flags remain centralized in `loop_form.rs`.
|
|
||||||
|
|
||||||
## Submodules
|
|
||||||
- `control.rs`: break/continue capture + predecessor bookkeeping
|
|
||||||
- `loop_form.rs`: main loop lowering pipeline
|
|
||||||
- `statements.rs`: loop-body statement lowering entry point
|
|
||||||
- `if_lowering.rs`: in-loop `if` lowering with JoinIR/PHI bridge
|
|
||||||
- `phi_ops.rs`: PHI emit helpers + LoopFormOps/PhiBuilderOps impls
|
|
||||||
@ -1,94 +0,0 @@
|
|||||||
use super::{ConstValue, LoopBuilder, ValueId};
|
|
||||||
use crate::mir::BasicBlockId;
|
|
||||||
use crate::runtime::get_global_ring0;
|
|
||||||
|
|
||||||
/// ループ脱出の種類(箱化・共通化のための型)
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub(super) enum LoopExitKind {
|
|
||||||
/// break文(exit blockへジャンプ)
|
|
||||||
Break,
|
|
||||||
/// continue文(header blockへジャンプ)
|
|
||||||
Continue,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> LoopBuilder<'a> {
|
|
||||||
/// Emit a jump to `target` from the current block and record predecessor metadata.
|
|
||||||
fn jump_with_pred(&mut self, target: BasicBlockId) -> Result<(), String> {
|
|
||||||
let cur_block = self.current_block()?;
|
|
||||||
self.emit_jump(target)?;
|
|
||||||
let _ = crate::mir::builder::loops::add_predecessor(self.parent_builder, target, cur_block);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// [LoopForm] 【箱化】ループ脱出の共通処理(break/continue統一化)
|
|
||||||
///
|
|
||||||
/// Phase 25.1o: break と continue の共通パターンを抽出し、
|
|
||||||
/// LoopExitKind で振る舞いを切り替える統一メソッド。
|
|
||||||
///
|
|
||||||
/// # 処理フロー
|
|
||||||
/// 1. 現在の変数マップをスナップショット
|
|
||||||
/// 2. [LoopForm] スナップショット保存(Break → exit_snapshots, Continue → continue_snapshots)
|
|
||||||
/// 3. Void を定義(戻り値用のダミー)
|
|
||||||
/// 4. [LoopForm] ターゲットブロックへジャンプ(Break → exit, Continue → header/continue_merge)
|
|
||||||
/// 5. 現在ブロックはジャンプで終端済みのまま維持(新しい unreachable ブロックは作らない)
|
|
||||||
fn do_loop_exit(&mut self, kind: LoopExitKind) -> Result<ValueId, String> {
|
|
||||||
// 1. スナップショット取得(共通処理)
|
|
||||||
let snapshot = self.get_current_variable_map();
|
|
||||||
let cur_block = self.current_block()?;
|
|
||||||
|
|
||||||
// 2. [LoopForm] exit-break path: スナップショット保存(exit PHI入力用)
|
|
||||||
// [LoopForm] continue-backedge path: スナップショット保存(continue_merge → header)
|
|
||||||
match kind {
|
|
||||||
LoopExitKind::Break => {
|
|
||||||
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
|
||||||
get_global_ring0().log.debug(&format!(
|
|
||||||
"[DEBUG/do_break] Saved snapshot from block {:?}, vars: {:?}",
|
|
||||||
cur_block,
|
|
||||||
snapshot.keys().collect::<Vec<_>>()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
self.exit_snapshots.push((cur_block, snapshot));
|
|
||||||
}
|
|
||||||
LoopExitKind::Continue => {
|
|
||||||
self.block_var_maps.insert(cur_block, snapshot.clone());
|
|
||||||
self.continue_snapshots.push((cur_block, snapshot));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 戻り値用のダミーを定義(現在ブロック内で完結させる)
|
|
||||||
let void_id = self.new_value();
|
|
||||||
self.emit_const(void_id, ConstValue::Void)?;
|
|
||||||
|
|
||||||
// 4. ターゲットブロックへジャンプ(kind別処理)
|
|
||||||
match kind {
|
|
||||||
LoopExitKind::Break => {
|
|
||||||
if let Some(exit_bb) = crate::mir::builder::loops::current_exit(self.parent_builder)
|
|
||||||
{
|
|
||||||
self.jump_with_pred(exit_bb)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LoopExitKind::Continue => {
|
|
||||||
// 既定では header にジャンプするが、canonical continue merge を導入した場合は
|
|
||||||
// continue_target 側を優先する。
|
|
||||||
if let Some(target) = self.continue_target.or(self.loop_header) {
|
|
||||||
self.jump_with_pred(target)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. 現在ブロックは jump で終端済み。新しい unreachable ブロックは作らない。
|
|
||||||
Ok(void_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle a `break` statement: jump to loop exit and continue in a fresh unreachable block.
|
|
||||||
/// 【箱化】do_loop_exit() への thin wrapper
|
|
||||||
pub(super) fn do_break(&mut self) -> Result<ValueId, String> {
|
|
||||||
self.do_loop_exit(LoopExitKind::Break)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle a `continue` statement: snapshot vars, jump to loop header, then continue in a fresh unreachable block.
|
|
||||||
/// 【箱化】do_loop_exit() への thin wrapper
|
|
||||||
pub(super) fn do_continue(&mut self) -> Result<ValueId, String> {
|
|
||||||
self.do_loop_exit(LoopExitKind::Continue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,298 +0,0 @@
|
|||||||
use super::{LoopBuilder, ValueId};
|
|
||||||
use crate::ast::ASTNode;
|
|
||||||
use crate::mir::control_form::{is_control_form_trace_on, ControlForm, IfShape};
|
|
||||||
use crate::mir::utils::{capture_actual_predecessor_and_jump, is_current_block_terminated};
|
|
||||||
use crate::mir::{BasicBlockId, ConstValue};
|
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
|
||||||
|
|
||||||
impl<'a> LoopBuilder<'a> {
|
|
||||||
/// Lower an if-statement inside a loop, preserving continue/break semantics and emitting PHIs per assigned variable.
|
|
||||||
pub(super) fn lower_if_in_loop(
|
|
||||||
&mut self,
|
|
||||||
condition: ASTNode,
|
|
||||||
then_body: Vec<ASTNode>,
|
|
||||||
else_body: Option<Vec<ASTNode>>,
|
|
||||||
) -> Result<ValueId, String> {
|
|
||||||
// Reserve a deterministic join id for debug region labeling (nested inside loop)
|
|
||||||
let join_id = self.parent_builder.debug_next_join_id();
|
|
||||||
// Pre-pin heuristic was deprecated; leave operands untouched for clarity.
|
|
||||||
// Evaluate condition and create blocks
|
|
||||||
let cond_val = self.parent_builder.build_expression(condition)?;
|
|
||||||
let then_bb = self.new_block();
|
|
||||||
let else_bb = self.new_block();
|
|
||||||
let merge_bb = self.new_block();
|
|
||||||
let pre_branch_bb = self.current_block()?;
|
|
||||||
self.emit_branch(cond_val, then_bb, else_bb)?;
|
|
||||||
|
|
||||||
// Capture pre-if variable map (used for phi normalization)
|
|
||||||
let pre_if_var_map = self.get_current_variable_map();
|
|
||||||
let trace_if = std::env::var("NYASH_IF_TRACE").ok().as_deref() == Some("1");
|
|
||||||
// (legacy) kept for earlier merge style; now unified helpers compute deltas directly.
|
|
||||||
|
|
||||||
// then branch
|
|
||||||
self.set_current_block(then_bb)?;
|
|
||||||
// Debug region: join then-branch (inside loop)
|
|
||||||
self.parent_builder
|
|
||||||
.debug_push_region(format!("join#{}", join_id) + "/then");
|
|
||||||
// Materialize all variables at entry via single-pred Phi (correctness-first)
|
|
||||||
let names_then: Vec<String> = self
|
|
||||||
.parent_builder
|
|
||||||
.variable_map
|
|
||||||
.keys()
|
|
||||||
.filter(|n| !n.starts_with("__pin$"))
|
|
||||||
.cloned()
|
|
||||||
.collect();
|
|
||||||
for name in names_then {
|
|
||||||
if let Some(&pre_v) = pre_if_var_map.get(&name) {
|
|
||||||
let phi_val = self.new_value();
|
|
||||||
self.emit_phi_at_block_start(then_bb, phi_val, vec![(pre_branch_bb, pre_v)])?;
|
|
||||||
let name_for_log = name.clone();
|
|
||||||
self.update_variable(name, phi_val);
|
|
||||||
if trace_if {
|
|
||||||
eprintln!(
|
|
||||||
"[if-trace] then-entry phi var={} pre={:?} -> dst={:?}",
|
|
||||||
name_for_log, pre_v, phi_val
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for s in then_body.iter().cloned() {
|
|
||||||
let _ = self.build_statement(s)?;
|
|
||||||
// フェーズS修正:統一終端検出ユーティリティ使用
|
|
||||||
if is_current_block_terminated(self.parent_builder)? {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let then_var_map_end = self.get_current_variable_map();
|
|
||||||
// フェーズS修正:最強モード指摘の「実到達predecessor捕捉」を統一
|
|
||||||
let _then_pred_to_merge =
|
|
||||||
capture_actual_predecessor_and_jump(self.parent_builder, merge_bb)?;
|
|
||||||
// Pop then-branch debug region
|
|
||||||
self.parent_builder.debug_pop_region();
|
|
||||||
|
|
||||||
// else branch
|
|
||||||
self.set_current_block(else_bb)?;
|
|
||||||
// Debug region: join else-branch (inside loop)
|
|
||||||
self.parent_builder
|
|
||||||
.debug_push_region(format!("join#{}", join_id) + "/else");
|
|
||||||
// Materialize all variables at entry via single-pred Phi (correctness-first)
|
|
||||||
let names2: Vec<String> = self
|
|
||||||
.parent_builder
|
|
||||||
.variable_map
|
|
||||||
.keys()
|
|
||||||
.filter(|n| !n.starts_with("__pin$"))
|
|
||||||
.cloned()
|
|
||||||
.collect();
|
|
||||||
for name in names2 {
|
|
||||||
if let Some(&pre_v) = pre_if_var_map.get(&name) {
|
|
||||||
let phi_val = self.new_value();
|
|
||||||
self.emit_phi_at_block_start(else_bb, phi_val, vec![(pre_branch_bb, pre_v)])?;
|
|
||||||
let name_for_log = name.clone();
|
|
||||||
self.update_variable(name, phi_val);
|
|
||||||
if trace_if {
|
|
||||||
eprintln!(
|
|
||||||
"[if-trace] else-entry phi var={} pre={:?} -> dst={:?}",
|
|
||||||
name_for_log, pre_v, phi_val
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut else_var_map_end_opt: Option<BTreeMap<String, ValueId>> = None;
|
|
||||||
if let Some(es) = else_body.clone() {
|
|
||||||
for s in es.into_iter() {
|
|
||||||
let _ = self.build_statement(s)?;
|
|
||||||
// フェーズS修正:統一終端検出ユーティリティ使用
|
|
||||||
if is_current_block_terminated(self.parent_builder)? {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else_var_map_end_opt = Some(self.get_current_variable_map());
|
|
||||||
}
|
|
||||||
// フェーズS修正:else branchでも統一実到達predecessor捕捉
|
|
||||||
let _else_pred_to_merge =
|
|
||||||
capture_actual_predecessor_and_jump(self.parent_builder, merge_bb)?;
|
|
||||||
// Pop else-branch debug region
|
|
||||||
self.parent_builder.debug_pop_region();
|
|
||||||
|
|
||||||
// Continue at merge
|
|
||||||
self.set_current_block(merge_bb)?;
|
|
||||||
// Debug region: join merge (inside loop)
|
|
||||||
self.parent_builder
|
|
||||||
.debug_push_region(format!("join#{}", join_id) + "/join");
|
|
||||||
|
|
||||||
// Phase 25.1: HashSet → BTreeSet(決定性確保)
|
|
||||||
// Phase 40-4.1: JoinIR経路をデフォルト化(collect_assigned_vars削除)
|
|
||||||
// Phase 84-5: if_phi.rs 削除 → test_utils に移動
|
|
||||||
let _vars: BTreeSet<String> =
|
|
||||||
crate::mir::phi_core::test_utils::collect_assigned_vars_via_joinir(
|
|
||||||
&then_body,
|
|
||||||
else_body.as_ref(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Phase 26-E: PhiBuilderOps trait 実装(箱理論統一)
|
|
||||||
struct Ops<'b, 'a>(&'b mut LoopBuilder<'a>);
|
|
||||||
|
|
||||||
// Phase 26-E: PhiBuilderOps trait 実装(箱理論統一)
|
|
||||||
impl<'b, 'a> crate::mir::phi_core::phi_builder_box::PhiBuilderOps for Ops<'b, 'a> {
|
|
||||||
fn new_value(&mut self) -> ValueId {
|
|
||||||
self.0.new_value()
|
|
||||||
}
|
|
||||||
fn emit_phi(
|
|
||||||
&mut self,
|
|
||||||
block: BasicBlockId,
|
|
||||||
dst: ValueId,
|
|
||||||
inputs: Vec<(BasicBlockId, ValueId)>,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
self.0.emit_phi_at_block_start(block, dst, inputs)
|
|
||||||
}
|
|
||||||
fn update_var(&mut self, name: String, value: ValueId) {
|
|
||||||
self.0.parent_builder.variable_map.insert(name, value);
|
|
||||||
}
|
|
||||||
fn get_block_predecessors(&self, block: BasicBlockId) -> Vec<BasicBlockId> {
|
|
||||||
if let Some(ref func) = self.0.parent_builder.current_function {
|
|
||||||
func.blocks
|
|
||||||
.get(&block)
|
|
||||||
.map(|bb| bb.predecessors.iter().copied().collect())
|
|
||||||
.unwrap_or_default()
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn emit_void(&mut self) -> ValueId {
|
|
||||||
let void_id = self.0.new_value();
|
|
||||||
let _ = self.0.emit_const(void_id, ConstValue::Void);
|
|
||||||
void_id
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 3-A: Loop PHI生成用メソッド実装
|
|
||||||
fn set_current_block(&mut self, block: BasicBlockId) -> Result<(), String> {
|
|
||||||
self.0.parent_builder.current_block = Some(block);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn block_exists(&self, block: BasicBlockId) -> bool {
|
|
||||||
if let Some(ref func) = self.0.parent_builder.current_function {
|
|
||||||
func.blocks.contains_key(&block)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 25.1h: ControlForm統合版に切り替え
|
|
||||||
let if_shape = IfShape {
|
|
||||||
cond_block: pre_branch_bb,
|
|
||||||
then_block: then_bb,
|
|
||||||
else_block: Some(else_bb),
|
|
||||||
merge_block: merge_bb,
|
|
||||||
};
|
|
||||||
let form = ControlForm::from_if(if_shape.clone());
|
|
||||||
|
|
||||||
// Region/GC 観測レイヤ(Phase 25.1l):
|
|
||||||
// NYASH_REGION_TRACE=1 のときだけ、Stage‑B 周辺 If 構造の
|
|
||||||
// Region 情報(entry/exit/slots)をログに出すよ。
|
|
||||||
crate::mir::region::observer::observe_control_form(self.parent_builder, &form);
|
|
||||||
|
|
||||||
// Phase 61-1: If-in-loop JoinIR化(開発フラグ制御)
|
|
||||||
// carrier_namesを作成(両経路で共通)
|
|
||||||
let carrier_names: BTreeSet<String> = pre_if_var_map
|
|
||||||
.keys()
|
|
||||||
.filter(|name| !name.starts_with("__pin$")) // 一時変数除外
|
|
||||||
.cloned()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Phase 62-B: JoinIRIfPhiSelector箱化(-60行の簡潔化)
|
|
||||||
let joinir_result = if crate::config::env::joinir_if_select_enabled() {
|
|
||||||
if let Some(ref func) = self.parent_builder.current_function {
|
|
||||||
let selector =
|
|
||||||
super::JoinIRIfPhiSelector::new(func, pre_branch_bb, carrier_names.clone());
|
|
||||||
selector.try_lower()
|
|
||||||
} else {
|
|
||||||
super::JoinIRResult {
|
|
||||||
success: false,
|
|
||||||
phi_spec: None,
|
|
||||||
join_inst: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
super::JoinIRResult {
|
|
||||||
success: false,
|
|
||||||
phi_spec: None,
|
|
||||||
join_inst: None,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let joinir_success = joinir_result.success;
|
|
||||||
let joinir_phi_spec_opt = joinir_result.phi_spec;
|
|
||||||
|
|
||||||
let mut ops = Ops(self);
|
|
||||||
|
|
||||||
// Phase 61-3: JoinIR本番経路(IfInLoopPhiEmitter)
|
|
||||||
if joinir_success {
|
|
||||||
if let Some(ref joinir_spec) = joinir_phi_spec_opt {
|
|
||||||
// IfInLoopPhiEmitter を使用してPHI生成
|
|
||||||
let else_snap_opt = else_var_map_end_opt.as_ref();
|
|
||||||
let phi_count = super::IfInLoopPhiEmitter::emit_header_phis(
|
|
||||||
joinir_spec,
|
|
||||||
&pre_if_var_map,
|
|
||||||
&then_var_map_end,
|
|
||||||
else_snap_opt,
|
|
||||||
&carrier_names,
|
|
||||||
&mut ops,
|
|
||||||
&if_shape,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if crate::config::env::joinir_if_in_loop_dryrun_enabled() {
|
|
||||||
eprintln!(
|
|
||||||
"[Phase 61-3] ✅ IfInLoopPhiEmitter generated {} PHIs",
|
|
||||||
phi_count
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// フォールバック: PhiBuilderBox経路(既存)
|
|
||||||
if !joinir_success {
|
|
||||||
// Phase 26-E: PhiBuilderBox SSOT統合(If PHI生成)
|
|
||||||
// Legacy: merge_modified_with_control() → New: PhiBuilderBox::generate_phis()
|
|
||||||
// Phase 61-6.1: set_if_context削除、直接IfPhiContextを生成
|
|
||||||
let mut phi_builder = crate::mir::phi_core::phi_builder_box::PhiBuilderBox::new();
|
|
||||||
phi_builder.if_context = Some(crate::mir::phi_core::phi_builder_box::IfPhiContext {
|
|
||||||
in_loop_body: true,
|
|
||||||
loop_carrier_names: carrier_names.clone(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Phase 35-5: if_body_local_merge.rs削除、ロジックはPhiBuilderBox内に統合
|
|
||||||
let post_snapshots = if let Some(ref else_map) = else_var_map_end_opt {
|
|
||||||
vec![then_var_map_end.clone(), else_map.clone()]
|
|
||||||
} else {
|
|
||||||
vec![then_var_map_end.clone()]
|
|
||||||
};
|
|
||||||
phi_builder.generate_phis(&mut ops, &form, &pre_if_var_map, &post_snapshots)?;
|
|
||||||
|
|
||||||
// Phase 61-6.2: A/B比較削除(JoinIR経路完全動作確認済み)
|
|
||||||
// Phase 61-3でJoinIR経路が完全動作したため、観察コード削除
|
|
||||||
// SSOT: JoinIR → compute_phi_spec_from_joinir()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 26-E-4: PHI生成後に variable_map をリセット(ChatGPT/Task先生指示)
|
|
||||||
// 理由: else_var_map_end_opt が正しい snapshot を保持したまま PHI 生成に渡す必要がある
|
|
||||||
// 修正前: PHI生成前にリセット → else ブロック内定義変数が消失 → domination error
|
|
||||||
// 修正後: PHI生成後にリセット → 正しいPHI入力 → SSA保証
|
|
||||||
self.parent_builder.variable_map = pre_if_var_map.clone();
|
|
||||||
|
|
||||||
// ControlForm 観測: 環境フラグ(未設定時は既定ON)のとき IfShape をダンプ
|
|
||||||
if is_control_form_trace_on() {
|
|
||||||
form.debug_dump();
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
if let Some(ref func) = self.parent_builder.current_function {
|
|
||||||
if_shape.debug_validate(func);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let void_id = self.new_value();
|
|
||||||
self.emit_const(void_id, ConstValue::Void)?;
|
|
||||||
// Pop merge debug region
|
|
||||||
self.parent_builder.debug_pop_region();
|
|
||||||
Ok(void_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,168 +0,0 @@
|
|||||||
//! Phase 62-B: JoinIR If-PHI Selector
|
|
||||||
//!
|
|
||||||
//! If-in-loop の JoinIR lowering 試行と PhiSpec 生成を担当する箱。
|
|
||||||
//!
|
|
||||||
//! ## 箱理論における位置づけ
|
|
||||||
//!
|
|
||||||
//! ```text
|
|
||||||
//! ┌─────────────────────────────────────────────────────────────┐
|
|
||||||
//! │ if_lowering.rs(オーケストレーター) │
|
|
||||||
//! │ ↓ │
|
|
||||||
//! │ ┌─────────────────────┐ ┌─────────────────────────────┐ │
|
|
||||||
//! │ │ JoinIRIfPhiSelector │→ │ IfInLoopPhiEmitter │ │
|
|
||||||
//! │ │ (試行・PhiSpec生成) │ │ (PHI命令発行) │ │
|
|
||||||
//! │ └─────────────────────┘ └─────────────────────────────┘ │
|
|
||||||
//! └─────────────────────────────────────────────────────────────┘
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! ## 責務
|
|
||||||
//!
|
|
||||||
//! - JoinIR lowering の試行(try_lower_if_to_joinir 呼び出し)
|
|
||||||
//! - PhiSpec の計算(compute_phi_spec_from_joinir 呼び出し)
|
|
||||||
//! - dry-run モード時のログ出力
|
|
||||||
//! - 本番/dry-run 切り替え判定
|
|
||||||
//!
|
|
||||||
//! ## 設計原則
|
|
||||||
//!
|
|
||||||
//! - **Thin Box**: JoinIR/PhiSpec計算は既存関数に委譲
|
|
||||||
//! - **状態保持**: 試行結果とPhiSpecを返却
|
|
||||||
//! - **ログ制御**: dry-runフラグに応じた詳細ログ
|
|
||||||
|
|
||||||
use crate::mir::join_ir::lowering::if_phi_context::IfPhiContext;
|
|
||||||
use crate::mir::join_ir::lowering::if_phi_spec::{compute_phi_spec_from_joinir, PhiSpec};
|
|
||||||
use crate::mir::join_ir::lowering::try_lower_if_to_joinir;
|
|
||||||
use crate::mir::join_ir::JoinInst;
|
|
||||||
use crate::mir::{BasicBlockId, MirFunction};
|
|
||||||
use crate::runtime::get_global_ring0;
|
|
||||||
use std::collections::BTreeSet;
|
|
||||||
|
|
||||||
/// JoinIR If-PHI Selector の試行結果
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct JoinIRResult {
|
|
||||||
/// JoinIR lowering 成功フラグ
|
|
||||||
pub success: bool,
|
|
||||||
|
|
||||||
/// 計算された PhiSpec(成功時のみ)
|
|
||||||
pub phi_spec: Option<PhiSpec>,
|
|
||||||
|
|
||||||
/// lowering された JoinInst(デバッグ用)
|
|
||||||
pub join_inst: Option<JoinInst>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// JoinIR If-PHI Selector
|
|
||||||
///
|
|
||||||
/// If-in-loop の JoinIR 経路を試行し、PhiSpec を生成する。
|
|
||||||
pub struct JoinIRIfPhiSelector<'a> {
|
|
||||||
/// If-PHI コンテキスト
|
|
||||||
context: IfPhiContext,
|
|
||||||
|
|
||||||
/// 現在の MIR 関数
|
|
||||||
func: &'a MirFunction,
|
|
||||||
|
|
||||||
/// 分岐前ブロック
|
|
||||||
pre_branch_bb: BasicBlockId,
|
|
||||||
|
|
||||||
/// dry-run モード
|
|
||||||
dryrun: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> JoinIRIfPhiSelector<'a> {
|
|
||||||
/// JoinIR If-PHI Selector を作成
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `func` - 現在の MIR 関数
|
|
||||||
/// * `pre_branch_bb` - 分岐前ブロック
|
|
||||||
/// * `carrier_names` - ループキャリア変数名
|
|
||||||
pub fn new(
|
|
||||||
func: &'a MirFunction,
|
|
||||||
pre_branch_bb: BasicBlockId,
|
|
||||||
carrier_names: BTreeSet<String>,
|
|
||||||
) -> Self {
|
|
||||||
let context = IfPhiContext::for_loop_body(carrier_names);
|
|
||||||
let dryrun = crate::config::env::joinir_dev_enabled()
|
|
||||||
&& crate::config::env::joinir_if_in_loop_dryrun_enabled();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
context,
|
|
||||||
func,
|
|
||||||
pre_branch_bb,
|
|
||||||
dryrun,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// JoinIR lowering を試行
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// JoinIR 試行結果(success, phi_spec, join_inst)
|
|
||||||
pub fn try_lower(&self) -> JoinIRResult {
|
|
||||||
// JoinIR lowering 試行
|
|
||||||
match try_lower_if_to_joinir(self.func, self.pre_branch_bb, false, Some(&self.context)) {
|
|
||||||
Some(join_inst) => {
|
|
||||||
if self.dryrun {
|
|
||||||
get_global_ring0()
|
|
||||||
.log
|
|
||||||
.debug(&format!("[Phase 61-2] ✅ If-in-loop lowered via JoinIR: {:?}", join_inst));
|
|
||||||
}
|
|
||||||
|
|
||||||
// PhiSpec 計算
|
|
||||||
let phi_spec = compute_phi_spec_from_joinir(&self.context, &join_inst);
|
|
||||||
|
|
||||||
// dry-run ログ
|
|
||||||
if self.dryrun {
|
|
||||||
self.log_dryrun(&join_inst, &phi_spec);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 本番経路有効判定(Core + フラグ)
|
|
||||||
let success = crate::config::env::joinir_core_enabled()
|
|
||||||
&& crate::config::env::joinir_if_in_loop_enable();
|
|
||||||
|
|
||||||
JoinIRResult {
|
|
||||||
success,
|
|
||||||
phi_spec: Some(phi_spec),
|
|
||||||
join_inst: Some(join_inst),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
if self.dryrun {
|
|
||||||
get_global_ring0()
|
|
||||||
.log
|
|
||||||
.debug("[Phase 61-2] ⏭️ JoinIR pattern not matched, using fallback");
|
|
||||||
}
|
|
||||||
|
|
||||||
JoinIRResult {
|
|
||||||
success: false,
|
|
||||||
phi_spec: None,
|
|
||||||
join_inst: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// dry-run モード時の詳細ログ出力
|
|
||||||
fn log_dryrun(&self, join_inst: &JoinInst, phi_spec: &PhiSpec) {
|
|
||||||
get_global_ring0()
|
|
||||||
.log
|
|
||||||
.debug("[Phase 61-2] 🔍 dry-run mode enabled");
|
|
||||||
get_global_ring0().log.debug(&format!(
|
|
||||||
"[Phase 61-2] Carrier variables: {:?}",
|
|
||||||
self.context.carrier_names
|
|
||||||
));
|
|
||||||
get_global_ring0().log.debug(&format!(
|
|
||||||
"[Phase 61-2] JoinInst type: {}",
|
|
||||||
match join_inst {
|
|
||||||
JoinInst::Select { .. } => "Select",
|
|
||||||
JoinInst::IfMerge { .. } => "IfMerge",
|
|
||||||
_ => "Other",
|
|
||||||
}
|
|
||||||
));
|
|
||||||
get_global_ring0().log.debug(&format!(
|
|
||||||
"[Phase 61-2] JoinIR PhiSpec: header={}, exit={}",
|
|
||||||
phi_spec.header_count(),
|
|
||||||
phi_spec.exit_count()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// テストは if_lowering.rs の統合テストで検証
|
|
||||||
@ -1,578 +0,0 @@
|
|||||||
use super::{ConstValue, LoopBuilder, ValueId};
|
|
||||||
use crate::ast::ASTNode;
|
|
||||||
use crate::mir::control_form::{is_control_form_trace_on, ControlForm, LoopShape};
|
|
||||||
use crate::mir::phi_core::loopform_builder::{LoopFormBuilder, LoopFormOps};
|
|
||||||
use crate::mir::utils::is_current_block_terminated;
|
|
||||||
use crate::mir::{BasicBlockId, MirInstruction};
|
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
|
||||||
|
|
||||||
impl<'a> LoopBuilder<'a> {
|
|
||||||
/// SSA形式でループを構築 (LoopForm v2 only)
|
|
||||||
pub fn build_loop(
|
|
||||||
&mut self,
|
|
||||||
condition: ASTNode,
|
|
||||||
body: Vec<ASTNode>,
|
|
||||||
) -> Result<ValueId, String> {
|
|
||||||
// Phase 7-F: Legacy loop builder removed - LoopForm v2 is now the only implementation
|
|
||||||
self.build_loop_with_loopform(condition, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// SSA形式でループを構築 (LoopFormBuilder implementation)
|
|
||||||
fn build_loop_with_loopform(
|
|
||||||
&mut self,
|
|
||||||
condition: ASTNode,
|
|
||||||
body: Vec<ASTNode>,
|
|
||||||
) -> Result<ValueId, String> {
|
|
||||||
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
|
||||||
eprintln!("[build_loop_with_loopform] === ENTRY ===");
|
|
||||||
if let Some(ref func) = self.parent_builder.current_function {
|
|
||||||
eprintln!(
|
|
||||||
"[build_loop_with_loopform] fn='{}', counter={}, func_ptr={:p}",
|
|
||||||
func.signature.name, func.next_value_id, func as *const _
|
|
||||||
);
|
|
||||||
}
|
|
||||||
eprintln!("[build_loop_with_loopform] condition={:?}", condition);
|
|
||||||
eprintln!("[build_loop_with_loopform] body.len()={}", body.len());
|
|
||||||
}
|
|
||||||
// Create loop structure blocks following LLVM canonical form
|
|
||||||
// We need a dedicated preheader block to materialize loop entry copies
|
|
||||||
let before_loop_id = self.current_block()?;
|
|
||||||
|
|
||||||
// Capture variable snapshot BEFORE creating new blocks (at loop entry point)
|
|
||||||
let current_vars = self.get_current_variable_map();
|
|
||||||
|
|
||||||
// DEBUG: Show variable map before guard check
|
|
||||||
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!(
|
|
||||||
"[loopform] before_loop_id={:?}, variable_map size={}",
|
|
||||||
before_loop_id,
|
|
||||||
current_vars.len()
|
|
||||||
));
|
|
||||||
for (name, value) in ¤t_vars {
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!(" {} -> {:?}", name, value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 25.3: GUARD check removed - ValueId(0) is valid for first parameters
|
|
||||||
// Previous code incorrectly assumed ValueId(0) always meant uninitialized variables,
|
|
||||||
// but it's actually the correct ID for the first parameter in functions like:
|
|
||||||
// skip_whitespace(s, idx) -> s=ValueId(0), idx=ValueId(1)
|
|
||||||
// This caused loops in such functions to be entirely skipped.
|
|
||||||
|
|
||||||
let preheader_id = self.new_block();
|
|
||||||
let header_id = self.new_block();
|
|
||||||
let body_id = self.new_block();
|
|
||||||
let latch_id = self.new_block();
|
|
||||||
let exit_id = self.new_block();
|
|
||||||
// Phase 25.1q: canonical continue merge block
|
|
||||||
// All continue 文は一度このブロックに集約してから header へ戻る。
|
|
||||||
let continue_merge_id = self.new_block();
|
|
||||||
|
|
||||||
// Jump from current block to preheader
|
|
||||||
let entry_block = self.current_block()?;
|
|
||||||
self.emit_jump(preheader_id)?;
|
|
||||||
// 📦 Hotfix 6: Add CFG predecessor for preheader (same as legacy version)
|
|
||||||
crate::mir::builder::loops::add_predecessor(
|
|
||||||
self.parent_builder,
|
|
||||||
preheader_id,
|
|
||||||
entry_block,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Initialize LoopFormBuilder with preheader and header blocks
|
|
||||||
let mut loopform = LoopFormBuilder::new(preheader_id, header_id);
|
|
||||||
|
|
||||||
// Pass 1: Prepare structure (allocate all ValueIds upfront)
|
|
||||||
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!(
|
|
||||||
"[loopform] Block IDs: preheader={:?}, header={:?}, body={:?}, latch={:?}, exit={:?}",
|
|
||||||
preheader_id, header_id, body_id, latch_id, exit_id
|
|
||||||
));
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!(
|
|
||||||
"[loopform] variable_map at loop entry (size={}):",
|
|
||||||
current_vars.len()
|
|
||||||
));
|
|
||||||
let mut loop_count = 0;
|
|
||||||
for (name, value) in ¤t_vars {
|
|
||||||
loop_count += 1;
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!(" [{}] {} -> {:?}", loop_count, name, value));
|
|
||||||
// Phase 26-A-4: ValueIdベース判定に変更(名前ベース → 型安全)
|
|
||||||
let is_param = self.is_parameter(*value);
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!(" param={}", is_param));
|
|
||||||
}
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!("[loopform] iterated {} times", loop_count));
|
|
||||||
if let Some(ref func) = self.parent_builder.current_function {
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!(
|
|
||||||
"[loopform] BEFORE prepare_structure: fn='{}', counter={}, func_ptr={:p}",
|
|
||||||
func.signature.name, func.next_value_id, func as *const _
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
crate::runtime::get_global_ring0().log.debug("[loopform] BEFORE prepare_structure: current_function=None");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loopform.prepare_structure(self, ¤t_vars)?;
|
|
||||||
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
|
||||||
if let Some(ref func) = self.parent_builder.current_function {
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!(
|
|
||||||
"[loopform] AFTER prepare_structure: fn='{}', counter={}, func_ptr={:p}",
|
|
||||||
func.signature.name, func.next_value_id, func as *const _
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
crate::runtime::get_global_ring0().log.debug("[loopform] AFTER prepare_structure: current_function=None");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass 2: Emit preheader (copies and jump to header)
|
|
||||||
loopform.emit_preheader(self)?;
|
|
||||||
// 📦 Hotfix 6: Add CFG predecessor for header from preheader (same as legacy version)
|
|
||||||
crate::mir::builder::loops::add_predecessor(self.parent_builder, header_id, preheader_id)?;
|
|
||||||
|
|
||||||
// Pass 3: Emit header PHIs (incomplete, only preheader edge)
|
|
||||||
self.set_current_block(header_id)?;
|
|
||||||
|
|
||||||
// Ensure header block exists before emitting PHIs
|
|
||||||
self.parent_builder.ensure_block_exists(header_id)?;
|
|
||||||
|
|
||||||
// Phase 27.4-C Refactor: JoinIR Loop φ バイパスフラグ統一取得
|
|
||||||
let fn_name = self
|
|
||||||
.parent_builder
|
|
||||||
.current_function
|
|
||||||
.as_ref()
|
|
||||||
.map(|f| f.signature.name.clone())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let bypass_flags = crate::mir::phi_core::loopform_builder::get_loop_bypass_flags(&fn_name);
|
|
||||||
|
|
||||||
if bypass_flags.header {
|
|
||||||
// Phase 27.4-C: JoinIR 実験経路では Header φ を生成しない。
|
|
||||||
// Pinned/Carrier の値は preheader の copy をそのまま使う。
|
|
||||||
//
|
|
||||||
// ⚠️ 重要: このモードでは MIR は不完全(φ 抜け)であり、VM で実行できない。
|
|
||||||
// JoinIR runner 専用モードであることに注意。
|
|
||||||
if crate::mir::phi_core::loopform_builder::is_loopform_debug_enabled() {
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!("[loopform/27.4-C] Header φ bypass active for: {}", fn_name));
|
|
||||||
crate::runtime::get_global_ring0().log.debug("[loopform/27.4-C] Skipping emit_header_phis() - using preheader values directly");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 従来どおり HeaderPhiBuilder を使って φ を準備
|
|
||||||
loopform.emit_header_phis(self)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if crate::mir::phi_core::loopform_builder::is_loopform_debug_enabled() {
|
|
||||||
crate::runtime::get_global_ring0().log.debug("[loopform] variable_map after emit_header_phis:");
|
|
||||||
for (name, value) in self.get_current_variable_map().iter() {
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!(" {} -> {:?}", name, value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up loop context for break/continue
|
|
||||||
crate::mir::builder::loops::push_loop_context(self.parent_builder, header_id, exit_id);
|
|
||||||
self.loop_header = Some(header_id);
|
|
||||||
// 既定の continue 先を canonical continue_merge ブロックにする。
|
|
||||||
// ここを差し替えることで do_loop_exit(Continue) のターゲットを一元化する。
|
|
||||||
self.continue_target = Some(continue_merge_id);
|
|
||||||
self.continue_snapshots.clear();
|
|
||||||
self.exit_snapshots.clear();
|
|
||||||
|
|
||||||
// [LoopForm] header-cond: cond true → body, false → exit (Case A/B)
|
|
||||||
// - Case A: loop(i < n) → header can branch to exit directly
|
|
||||||
// - Case B: loop(1 == 1) → header always enters body, exit only via break
|
|
||||||
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!(
|
|
||||||
"[loopform/condition] BEFORE build_expression: current_block={:?}",
|
|
||||||
self.current_block()?
|
|
||||||
));
|
|
||||||
if let Some(ref func) = self.parent_builder.current_function {
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!(
|
|
||||||
"[loopform/condition] BEFORE: fn='{}', counter={}, func_ptr={:p}",
|
|
||||||
func.signature.name, func.next_value_id, func as *const _
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let cond_value = self.parent_builder.build_expression(condition)?;
|
|
||||||
// Capture the ACTUAL block that emits the branch (might differ from header_id
|
|
||||||
// if build_expression created new blocks)
|
|
||||||
let branch_source_block = self.current_block()?;
|
|
||||||
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!(
|
|
||||||
"[loopform/condition] AFTER build_expression: branch_source_block={:?}",
|
|
||||||
branch_source_block
|
|
||||||
));
|
|
||||||
if let Some(ref func) = self.parent_builder.current_function {
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!(
|
|
||||||
"[loopform/condition] AFTER: fn='{}', counter={}, func_ptr={:p}",
|
|
||||||
func.signature.name, func.next_value_id, func as *const _
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.emit_branch(cond_value, body_id, exit_id)?;
|
|
||||||
// 📦 Hotfix 6: Add CFG predecessors for branch targets (Cytron et al. 1991 requirement)
|
|
||||||
// This ensures exit_block.predecessors is populated before Exit PHI generation
|
|
||||||
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!(
|
|
||||||
"[loopform/condition] BEFORE add_predecessor: exit_id={:?}, branch_source={:?}",
|
|
||||||
exit_id, branch_source_block
|
|
||||||
));
|
|
||||||
}
|
|
||||||
crate::mir::builder::loops::add_predecessor(
|
|
||||||
self.parent_builder,
|
|
||||||
body_id,
|
|
||||||
branch_source_block,
|
|
||||||
)?;
|
|
||||||
crate::mir::builder::loops::add_predecessor(
|
|
||||||
self.parent_builder,
|
|
||||||
exit_id,
|
|
||||||
branch_source_block,
|
|
||||||
)?;
|
|
||||||
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!(
|
|
||||||
"[loopform/condition] AFTER emit_branch: current_block={:?}",
|
|
||||||
self.current_block()?
|
|
||||||
));
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!(
|
|
||||||
"[loopform/condition] Added predecessors: body={:?} exit={:?} from={:?}",
|
|
||||||
body_id, exit_id, branch_source_block
|
|
||||||
));
|
|
||||||
// Verify predecessors were added
|
|
||||||
if let Some(ref func) = self.parent_builder.current_function {
|
|
||||||
if let Some(exit_block) = func.blocks.get(&exit_id) {
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!(
|
|
||||||
"[loopform/condition] exit_block.predecessors = {:?}",
|
|
||||||
exit_block.predecessors
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lower loop body
|
|
||||||
self.set_current_block(body_id)?;
|
|
||||||
for stmt in body {
|
|
||||||
self.build_statement(stmt)?;
|
|
||||||
if is_current_block_terminated(self.parent_builder)? {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capture variable snapshot at end of body (before jumping to latch)
|
|
||||||
let body_end_vars = self.get_current_variable_map();
|
|
||||||
|
|
||||||
// Step 5-1: Writes集合収集(選択肢2+3統合: Snapshot比較で再代入検出)
|
|
||||||
// current_vars (preheader) と body_end_vars を比較し、ValueId が変わった変数を特定
|
|
||||||
use std::collections::HashSet;
|
|
||||||
let mut writes = HashSet::new();
|
|
||||||
for (name, &body_value) in &body_end_vars {
|
|
||||||
// Skip __pin$ temporary variables - they are always BodyLocalInternal
|
|
||||||
// (Task先生の発見: これらをcarrier扱いすると未定義ValueIdエラーの原因になる)
|
|
||||||
if name.starts_with("__pin$") && name.contains("$@") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(&base_value) = current_vars.get(name) {
|
|
||||||
if body_value != base_value {
|
|
||||||
writes.insert(name.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// else: body で新規定義された変数(body-local)、header PHI 不要
|
|
||||||
}
|
|
||||||
|
|
||||||
// DEBUG: Log writes collection
|
|
||||||
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
|
||||||
crate::runtime::get_global_ring0().log.debug("[loopform/writes] === WRITES COLLECTION (Step 5-1) ===");
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!(
|
|
||||||
"[loopform/writes] {} variables modified in loop body",
|
|
||||||
writes.len()
|
|
||||||
));
|
|
||||||
let mut sorted_writes: Vec<_> = writes.iter().collect();
|
|
||||||
sorted_writes.sort();
|
|
||||||
for name in &sorted_writes {
|
|
||||||
crate::runtime::get_global_ring0().log.debug(&format!("[loopform/writes] WRITE: {}", name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Jump to latch if not already terminated
|
|
||||||
let actual_latch_id = if !is_current_block_terminated(self.parent_builder)? {
|
|
||||||
self.emit_jump(latch_id)?;
|
|
||||||
latch_id
|
|
||||||
} else {
|
|
||||||
// Body is terminated (break/continue), use current block as latch
|
|
||||||
self.current_block()?
|
|
||||||
};
|
|
||||||
|
|
||||||
// Latch: jump back to header
|
|
||||||
self.set_current_block(latch_id)?;
|
|
||||||
|
|
||||||
// Update variable map with body end values for sealing
|
|
||||||
for (name, value) in &body_end_vars {
|
|
||||||
self.update_variable(name.clone(), *value);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.emit_jump(header_id)?;
|
|
||||||
// 📦 Hotfix 6: Add CFG predecessor for header from latch (same as legacy version)
|
|
||||||
crate::mir::builder::loops::add_predecessor(self.parent_builder, header_id, latch_id)?;
|
|
||||||
|
|
||||||
// Phase 25.1c/k: body-local 変数の PHI 生成
|
|
||||||
// BreakFinderBox / FuncScannerBox 等で、loop body 内で新規宣言された local 変数が
|
|
||||||
// loop header に戻った時に undefined になる問題を修正
|
|
||||||
//
|
|
||||||
// Step 5-5-B: EXPERIMENTAL - Body-local Header PHI generation DISABLED
|
|
||||||
// Reason: Option C design states body-local variables should NOT have header PHIs
|
|
||||||
// - BodyLocalExit: needs EXIT PHI only, NOT header PHI
|
|
||||||
// - BodyLocalInternal: needs NO PHI at all
|
|
||||||
//
|
|
||||||
// TODO Step 5-3: Integrate Option C classification (LoopScopeShape) here
|
|
||||||
//
|
|
||||||
// TEMPORARY DISABLE to test hypothesis that header PHIs are the root cause
|
|
||||||
let trace_loop_phi = std::env::var("HAKO_LOOP_PHI_TRACE").ok().as_deref() == Some("1");
|
|
||||||
|
|
||||||
// DISABLED: Body-local header PHI generation
|
|
||||||
// This code was causing undefined value errors because it created header PHIs
|
|
||||||
// for variables that should only have exit PHIs (or no PHIs at all)
|
|
||||||
if false { // Disabled for Step 5-5-B experiment
|
|
||||||
// [Original code removed - see git history if needed]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass 4: Generate continue_merge PHIs first, then seal header PHIs
|
|
||||||
// Phase 25.1c/k: canonical continue_merge ブロックで PHI を生成してから seal_phis を呼ぶ
|
|
||||||
let raw_continue_snaps = self.continue_snapshots.clone();
|
|
||||||
|
|
||||||
// Step 1: continue_merge ブロックで PHI 生成(merged_snapshot を作る)
|
|
||||||
// Phase 25.2: LoopSnapshotMergeBox を使って整理
|
|
||||||
self.set_current_block(continue_merge_id)?;
|
|
||||||
|
|
||||||
let merged_snapshot: BTreeMap<String, ValueId> = if !raw_continue_snaps.is_empty() {
|
|
||||||
if trace_loop_phi {
|
|
||||||
eprintln!(
|
|
||||||
"[loop-phi/continue-merge] Generating PHI nodes for {} continue paths",
|
|
||||||
raw_continue_snaps.len()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// すべての continue snapshot に現れる変数を収集
|
|
||||||
let mut all_vars: BTreeMap<String, Vec<(BasicBlockId, ValueId)>> = BTreeMap::new();
|
|
||||||
for (continue_bb, snapshot) in &raw_continue_snaps {
|
|
||||||
for (var_name, &value) in snapshot {
|
|
||||||
all_vars
|
|
||||||
.entry(var_name.clone())
|
|
||||||
.or_default()
|
|
||||||
.push((*continue_bb, value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 各変数について PHI ノードを生成
|
|
||||||
// ========================================
|
|
||||||
// Phase 59b: PhiInputCollector インライン化
|
|
||||||
// ========================================
|
|
||||||
let mut merged = BTreeMap::new();
|
|
||||||
for (var_name, inputs) in all_vars {
|
|
||||||
// Step 1: sanitize (BTreeMap で重複削除&ソート)
|
|
||||||
let mut sanitized: BTreeMap<BasicBlockId, ValueId> = BTreeMap::new();
|
|
||||||
for (bb, val) in &inputs {
|
|
||||||
sanitized.insert(*bb, *val);
|
|
||||||
}
|
|
||||||
let final_inputs: Vec<(BasicBlockId, ValueId)> = sanitized.into_iter().collect();
|
|
||||||
|
|
||||||
// Step 2: optimize_same_value
|
|
||||||
let same_value = if final_inputs.is_empty() {
|
|
||||||
None
|
|
||||||
} else if final_inputs.len() == 1 {
|
|
||||||
Some(final_inputs[0].1)
|
|
||||||
} else {
|
|
||||||
let first_val = final_inputs[0].1;
|
|
||||||
if final_inputs.iter().all(|(_, val)| *val == first_val) {
|
|
||||||
Some(first_val)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Step 3: PHI 生成 or 同一値を使用
|
|
||||||
let result_value = if let Some(same_val) = same_value {
|
|
||||||
// 全て同じ値 or 単一入力 → PHI 不要
|
|
||||||
same_val
|
|
||||||
} else {
|
|
||||||
// 異なる値を持つ場合は PHI ノードを生成
|
|
||||||
let phi_id = self.new_value();
|
|
||||||
|
|
||||||
if let Some(ref mut func) = self.parent_builder.current_function {
|
|
||||||
if let Some(merge_block) = func.blocks.get_mut(&continue_merge_id) {
|
|
||||||
merge_block.add_instruction(MirInstruction::Phi {
|
|
||||||
dst: phi_id,
|
|
||||||
inputs: final_inputs,
|
|
||||||
type_hint: None, // Phase 63-6
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if trace_loop_phi {
|
|
||||||
eprintln!(
|
|
||||||
"[loop-phi/continue-merge] Generated PHI for '{}': {:?}",
|
|
||||||
var_name, phi_id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
phi_id
|
|
||||||
};
|
|
||||||
|
|
||||||
merged.insert(var_name, result_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: 変数マップへの反映は seal_phis に委譲(干渉を避ける)
|
|
||||||
|
|
||||||
if trace_loop_phi {
|
|
||||||
eprintln!(
|
|
||||||
"[loop-phi/continue-merge] Merged {} variables from {} paths",
|
|
||||||
merged.len(),
|
|
||||||
raw_continue_snaps.len()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
merged
|
|
||||||
} else {
|
|
||||||
BTreeMap::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
self.emit_jump(header_id)?;
|
|
||||||
crate::mir::builder::loops::add_predecessor(
|
|
||||||
self.parent_builder,
|
|
||||||
header_id,
|
|
||||||
continue_merge_id,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Step 2: merged_snapshot を使って seal_phis を呼ぶ
|
|
||||||
// Phase 25.3: Continue merge PHI実装(Task先生の発見!)
|
|
||||||
// - continueが無いループでも、Latchブロックの値をHeader PHIに伝播する必要がある
|
|
||||||
// - これにより、Exit PHIがHeader PHI経由で正しい値を受け取れる
|
|
||||||
let continue_snaps: Vec<(BasicBlockId, BTreeMap<String, ValueId>)> = {
|
|
||||||
// まず、merged_snapshot(continue merge PHI結果)を追加
|
|
||||||
let mut snaps = if !merged_snapshot.is_empty() {
|
|
||||||
vec![(continue_merge_id, merged_snapshot.clone())]
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
};
|
|
||||||
|
|
||||||
// continueが無い場合でも、Latchブロックのスナップショットを追加
|
|
||||||
// これにより、seal_phis()がLatchからの値をHeader PHIに正しく接続できる
|
|
||||||
if raw_continue_snaps.is_empty() {
|
|
||||||
// continue文が無い場合、Latchブロックの現在の変数マップをキャプチャ
|
|
||||||
// Note: このタイミングでは current_block == exit_id だが、
|
|
||||||
// variable_map はLatch実行後の状態を保持している
|
|
||||||
let latch_snapshot = self.get_current_variable_map();
|
|
||||||
snaps.push((actual_latch_id, latch_snapshot));
|
|
||||||
}
|
|
||||||
|
|
||||||
snaps
|
|
||||||
};
|
|
||||||
|
|
||||||
// Phase 27.4C Refactor: Header φ バイパスフラグを統一取得(seal_phis に渡す)
|
|
||||||
// Note: fn_name は既に line 299-304 で取得済み、String として保持されている
|
|
||||||
let bypass_flags_for_seal =
|
|
||||||
crate::mir::phi_core::loopform_builder::get_loop_bypass_flags(&fn_name);
|
|
||||||
|
|
||||||
// Step 5-1/5-2: Pass writes 集合 for PHI縮約
|
|
||||||
// Phase 27.4C: header_bypass フラグも渡す
|
|
||||||
loopform.seal_phis(
|
|
||||||
self,
|
|
||||||
actual_latch_id,
|
|
||||||
&continue_snaps,
|
|
||||||
&writes,
|
|
||||||
bypass_flags_for_seal.header,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Step 3: seal body-local PHIs (complete the inputs)
|
|
||||||
// Step 5-5-A: REMOVED - PHIs now created complete with both inputs upfront
|
|
||||||
// Old sealing code was overwriting our preheader+latch inputs with latch-only,
|
|
||||||
// causing "phi pred mismatch" errors.
|
|
||||||
//
|
|
||||||
// Body-local PHIs are now created at line 408-456 with BOTH inputs:
|
|
||||||
// - preheader: poison value (variable doesn't exist yet)
|
|
||||||
// - latch: actual value from loop body
|
|
||||||
//
|
|
||||||
// No further sealing is needed!
|
|
||||||
|
|
||||||
// Exit block
|
|
||||||
self.set_current_block(exit_id)?;
|
|
||||||
|
|
||||||
// Phase 25.1h: ControlForm統合版に切り替え
|
|
||||||
// continue / break のターゲットブロックをユニーク化して収集
|
|
||||||
// Phase 25.1: HashSet → BTreeSet(決定性確保)
|
|
||||||
let mut break_set: BTreeSet<BasicBlockId> = BTreeSet::new();
|
|
||||||
for (bb, _) in &self.exit_snapshots {
|
|
||||||
break_set.insert(*bb);
|
|
||||||
}
|
|
||||||
// LoopShape の continue_targets は「header への canonical backedge」を表す。
|
|
||||||
// continue が一つ以上存在する場合は continue_merge_id を 1 つだけ登録する。
|
|
||||||
let continue_targets: Vec<BasicBlockId> = if self.continue_snapshots.is_empty() {
|
|
||||||
Vec::new()
|
|
||||||
} else {
|
|
||||||
vec![continue_merge_id]
|
|
||||||
};
|
|
||||||
let break_targets: Vec<BasicBlockId> = break_set.into_iter().collect();
|
|
||||||
|
|
||||||
let loop_shape = LoopShape {
|
|
||||||
preheader: preheader_id,
|
|
||||||
header: header_id,
|
|
||||||
body: body_id,
|
|
||||||
latch: latch_id,
|
|
||||||
exit: exit_id,
|
|
||||||
continue_targets,
|
|
||||||
break_targets,
|
|
||||||
};
|
|
||||||
let form = ControlForm::from_loop(loop_shape.clone());
|
|
||||||
|
|
||||||
// Region/GC 観測レイヤ(Phase 25.1l):
|
|
||||||
// NYASH_REGION_TRACE=1 のときだけ、Stage‑B 周辺ループの
|
|
||||||
// Region 情報(entry/exit/slots)をログに出すよ。
|
|
||||||
crate::mir::region::observer::observe_control_form(self.parent_builder, &form);
|
|
||||||
|
|
||||||
// Phase 27.6-2: JoinIR Exit φ バイパスチェック
|
|
||||||
let fn_name = self
|
|
||||||
.parent_builder
|
|
||||||
.current_function
|
|
||||||
.as_ref()
|
|
||||||
.map(|f| f.signature.name.as_str())
|
|
||||||
.unwrap_or("");
|
|
||||||
|
|
||||||
let exit_bypass = crate::mir::phi_core::loopform_builder::joinir_exit_bypass_enabled()
|
|
||||||
&& crate::mir::phi_core::loopform_builder::is_joinir_exit_bypass_target(fn_name);
|
|
||||||
|
|
||||||
if exit_bypass {
|
|
||||||
// Phase 27.6-2: JoinIR 実験経路では Exit φ を生成しない。
|
|
||||||
// ループ内で定義された値だけで exit 後を構成する(JoinIR の k_exit 引数として表現)。
|
|
||||||
//
|
|
||||||
// ⚠️ 重要: このモードでは MIR は不完全(Exit φ 抜け)であり、VM で実行できない。
|
|
||||||
// JoinIR runner 専用モードであることに注意。
|
|
||||||
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
|
||||||
eprintln!(
|
|
||||||
"[loopform/exit-bypass] func={} exit={:?} header={:?} (JoinIR experiment only)",
|
|
||||||
fn_name, exit_id, header_id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// [LoopForm] exit PHI for Case A/B (uses exit_snapshots + exit_preds)
|
|
||||||
// - Case A: header+break → exit PHI includes both paths
|
|
||||||
// - Case B: break-only → exit PHI excludes header (not a predecessor)
|
|
||||||
let exit_snaps = self.exit_snapshots.clone();
|
|
||||||
crate::mir::phi_core::loopform_builder::build_exit_phis_for_control(
|
|
||||||
&loopform,
|
|
||||||
self,
|
|
||||||
&form,
|
|
||||||
&exit_snaps,
|
|
||||||
branch_source_block,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pop loop context
|
|
||||||
crate::mir::builder::loops::pop_loop_context(self.parent_builder);
|
|
||||||
|
|
||||||
// ControlForm 観測: 環境フラグ(未設定時は既定ON)のとき LoopShape をダンプ
|
|
||||||
if is_control_form_trace_on() {
|
|
||||||
form.debug_dump();
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
if let Some(ref func) = self.parent_builder.current_function {
|
|
||||||
loop_shape.debug_validate(func);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return void value
|
|
||||||
let void_dst = self.new_value();
|
|
||||||
self.emit_const(void_dst, ConstValue::Void)?;
|
|
||||||
Ok(void_dst)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,158 +0,0 @@
|
|||||||
/*!
|
|
||||||
* MIR Loop Builder - SSA形式でのループ構築専用モジュール
|
|
||||||
*
|
|
||||||
* Sealed/Unsealed blockとPhi nodeを使った正しいループ実装。
|
|
||||||
*
|
|
||||||
* LoopForm v2 の「形」をここで固定している:
|
|
||||||
* - preheader: ループに入る直前のブロック(初期値の copy 発生源)
|
|
||||||
* - header : ループ条件を評価するブロック(`loop(cond)` の `cond` 部分)
|
|
||||||
* - body : ループ本体(ユーザーコードが書いたブロック)
|
|
||||||
* - latch : body の末尾から header へ戻る backedge 用ブロック
|
|
||||||
* - exit : ループ脱出先(`break` / `cond == false` が合流するブロック)
|
|
||||||
*
|
|
||||||
* 典型パターン(ControlForm::LoopShape):
|
|
||||||
* - Case A: header-cond + header→exit + body→exit(`loop(i < n) { if (...) break }`)
|
|
||||||
* - Case B: constant-true + body→exit のみ(`loop(1 == 1) { if (...) break }`)
|
|
||||||
* - この場合、header→exit のエッジは存在しないので、exit PHI に header 値を入れてはいけない。
|
|
||||||
* - Case C: continue_merge を経由して header に戻る経路あり(`continue` を含むループ)。
|
|
||||||
*
|
|
||||||
* それぞれのケースは ControlForm / LoopSnapshotMergeBox / ExitPhiBuilder に伝搬され、
|
|
||||||
* exit PHI の入力選択や BodyLocalInternal 変数の扱いに反映される。
|
|
||||||
*
|
|
||||||
* モジュール構成:
|
|
||||||
* - control.rs: break/continue 導線の共通化と predecessor 記録
|
|
||||||
* - loop_form.rs: LoopForm v2 本体の構築(preheader/header/body/latch/exit)
|
|
||||||
* - statements.rs: ループ内ステートメントの lowering(if は if_lowering.rs に委譲)
|
|
||||||
* - if_lowering.rs: ループ内 if の JoinIR/PHI 統合
|
|
||||||
* - phi_ops.rs: PHI 生成ヘルパーと LoopForm/PhiBuilder trait 実装
|
|
||||||
*/
|
|
||||||
|
|
||||||
mod control;
|
|
||||||
mod if_in_loop_phi_emitter;
|
|
||||||
mod if_lowering;
|
|
||||||
mod joinir_if_phi_selector;
|
|
||||||
mod loop_form;
|
|
||||||
mod phi_ops;
|
|
||||||
mod statements;
|
|
||||||
|
|
||||||
pub use if_in_loop_phi_emitter::IfInLoopPhiEmitter;
|
|
||||||
pub use joinir_if_phi_selector::{JoinIRIfPhiSelector, JoinIRResult};
|
|
||||||
|
|
||||||
use super::{BasicBlockId, ConstValue, MirInstruction, ValueId};
|
|
||||||
use std::collections::BTreeMap; // Phase 25.1: 決定性確保
|
|
||||||
|
|
||||||
/// ループビルダー - SSA形式でのループ構築を管理
|
|
||||||
pub struct LoopBuilder<'a> {
|
|
||||||
/// 親のMIRビルダーへの参照
|
|
||||||
pub(super) parent_builder: &'a mut super::builder::MirBuilder,
|
|
||||||
|
|
||||||
/// ブロックごとの変数マップ(スコープ管理)
|
|
||||||
/// Phase 25.1: BTreeMap → BTreeMap(決定性確保)
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(super) block_var_maps: BTreeMap<BasicBlockId, BTreeMap<String, ValueId>>,
|
|
||||||
|
|
||||||
/// ループヘッダーID(continue 先の既定値として使用)
|
|
||||||
pub(super) loop_header: Option<BasicBlockId>,
|
|
||||||
|
|
||||||
/// continue 文がジャンプするターゲットブロック
|
|
||||||
/// - 既定: header と同一
|
|
||||||
/// - 将来: canonical continue merge ブロックに差し替えるためのフック
|
|
||||||
pub(super) continue_target: Option<BasicBlockId>,
|
|
||||||
|
|
||||||
/// continue文からの変数スナップショット
|
|
||||||
pub(super) continue_snapshots: Vec<(BasicBlockId, BTreeMap<String, ValueId>)>,
|
|
||||||
|
|
||||||
/// break文からの変数スナップショット(exit PHI生成用)
|
|
||||||
pub(super) exit_snapshots: Vec<(BasicBlockId, BTreeMap<String, ValueId>)>,
|
|
||||||
// フェーズM: no_phi_modeフィールド削除(常にPHI使用)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> LoopBuilder<'a> {
|
|
||||||
/// 新しいループビルダーを作成
|
|
||||||
pub fn new(parent: &'a mut super::builder::MirBuilder) -> Self {
|
|
||||||
Self {
|
|
||||||
parent_builder: parent,
|
|
||||||
block_var_maps: BTreeMap::new(),
|
|
||||||
loop_header: None,
|
|
||||||
continue_target: None,
|
|
||||||
continue_snapshots: Vec::new(),
|
|
||||||
exit_snapshots: Vec::new(), // exit PHI用のスナップショット
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn current_block(&self) -> Result<BasicBlockId, String> {
|
|
||||||
self.parent_builder
|
|
||||||
.current_block
|
|
||||||
.ok_or_else(|| "No current block".to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn new_block(&mut self) -> BasicBlockId {
|
|
||||||
self.parent_builder.block_gen.next()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn new_value(&mut self) -> ValueId {
|
|
||||||
// Use function-local allocator via MirBuilder helper to keep
|
|
||||||
// ValueId ranges consistent within the current function.
|
|
||||||
self.parent_builder.next_value_id()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn set_current_block(&mut self, block_id: BasicBlockId) -> Result<(), String> {
|
|
||||||
self.parent_builder.start_new_block(block_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn emit_jump(&mut self, target: BasicBlockId) -> Result<(), String> {
|
|
||||||
self.parent_builder
|
|
||||||
.emit_instruction(MirInstruction::Jump { target })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn emit_branch(
|
|
||||||
&mut self,
|
|
||||||
condition: ValueId,
|
|
||||||
then_bb: BasicBlockId,
|
|
||||||
else_bb: BasicBlockId,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
// LocalSSA: ensure condition is materialized in the current block
|
|
||||||
let condition_local = self.parent_builder.local_ssa_ensure(condition, 4);
|
|
||||||
self.parent_builder
|
|
||||||
.emit_instruction(MirInstruction::Branch {
|
|
||||||
condition: condition_local,
|
|
||||||
then_bb,
|
|
||||||
else_bb,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn emit_const(&mut self, dst: ValueId, value: ConstValue) -> Result<(), String> {
|
|
||||||
self.parent_builder
|
|
||||||
.emit_instruction(MirInstruction::Const { dst, value })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn get_current_variable_map(&self) -> BTreeMap<String, ValueId> {
|
|
||||||
// Phase 25.1: BTreeMap化
|
|
||||||
self.parent_builder.variable_map.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn update_variable(&mut self, name: String, value: ValueId) {
|
|
||||||
if std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") {
|
|
||||||
eprintln!(
|
|
||||||
"[DEBUG] LoopBuilder::update_variable: name={}, value=%{}",
|
|
||||||
name, value.0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
self.parent_builder.variable_map.insert(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn get_variable_at_block(
|
|
||||||
&self,
|
|
||||||
name: &str,
|
|
||||||
block_id: BasicBlockId,
|
|
||||||
) -> Option<ValueId> {
|
|
||||||
// まずブロックごとのスナップショットを優先
|
|
||||||
if let Some(map) = self.block_var_maps.get(&block_id) {
|
|
||||||
if let Some(v) = map.get(name) {
|
|
||||||
return Some(*v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// フォールバック:現在の変数マップ(単純ケース用)
|
|
||||||
self.parent_builder.variable_map.get(name).copied()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,333 +0,0 @@
|
|||||||
use super::{LoopBuilder, ValueId};
|
|
||||||
use crate::mir::phi_core::loopform_builder::LoopFormOps;
|
|
||||||
use crate::mir::{BasicBlockId, ConstValue, MirInstruction};
|
|
||||||
use crate::runtime::get_global_ring0;
|
|
||||||
|
|
||||||
impl<'a> LoopBuilder<'a> {
|
|
||||||
/// ブロック先頭に PHI 命令を挿入(不変条件: PHI は常にブロック先頭)
|
|
||||||
pub(super) fn emit_phi_at_block_start(
|
|
||||||
&mut self,
|
|
||||||
block_id: BasicBlockId,
|
|
||||||
dst: ValueId,
|
|
||||||
inputs: Vec<(BasicBlockId, ValueId)>,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let dbg = std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1");
|
|
||||||
if dbg {
|
|
||||||
get_global_ring0().log.debug(&format!(
|
|
||||||
"[DEBUG] LoopBuilder::emit_phi_at_block_start: block={}, dst=%{}, inputs={:?}",
|
|
||||||
block_id, dst.0, inputs
|
|
||||||
));
|
|
||||||
}
|
|
||||||
// Phi nodeをブロックの先頭に挿入
|
|
||||||
if let Some(ref mut function) = self.parent_builder.current_function {
|
|
||||||
if let Some(block) = function.get_block_mut(block_id) {
|
|
||||||
if dbg {
|
|
||||||
get_global_ring0().log.debug(&format!(
|
|
||||||
"[DEBUG] Block {} current instructions count: {}",
|
|
||||||
block_id,
|
|
||||||
block.instructions.len()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
// Phi命令は必ずブロックの先頭に配置。ただし同一dstの既存PHIがある場合は差し替える。
|
|
||||||
let mut replaced = false;
|
|
||||||
let mut idx = 0;
|
|
||||||
let span = self.parent_builder.current_span;
|
|
||||||
while idx < block.instructions.len() {
|
|
||||||
match &mut block.instructions[idx] {
|
|
||||||
MirInstruction::Phi {
|
|
||||||
dst: d,
|
|
||||||
inputs: ins,
|
|
||||||
.. // Phase 63-6: Ignore type_hint in pattern matching
|
|
||||||
} if *d == dst => {
|
|
||||||
*ins = inputs.clone();
|
|
||||||
if block.instruction_spans.len() <= idx {
|
|
||||||
// Backfill missing spans to preserve alignment after legacy inserts.
|
|
||||||
while block.instruction_spans.len() <= idx {
|
|
||||||
block.instruction_spans.push(crate::ast::Span::unknown());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
block.instruction_spans[idx] = span;
|
|
||||||
replaced = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
MirInstruction::Phi { .. } => {
|
|
||||||
idx += 1;
|
|
||||||
}
|
|
||||||
_ => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !replaced {
|
|
||||||
let phi_inst = MirInstruction::Phi {
|
|
||||||
dst,
|
|
||||||
inputs: inputs.clone(),
|
|
||||||
type_hint: None, // Phase 63-6: Legacy path, no type hint
|
|
||||||
};
|
|
||||||
block.instructions.insert(0, phi_inst);
|
|
||||||
block.instruction_spans.insert(0, span);
|
|
||||||
}
|
|
||||||
if dbg {
|
|
||||||
get_global_ring0()
|
|
||||||
.log
|
|
||||||
.debug("[DEBUG] ✅ PHI instruction inserted at position 0");
|
|
||||||
get_global_ring0().log.debug(&format!(
|
|
||||||
"[DEBUG] Block {} after insert instructions count: {}",
|
|
||||||
block_id,
|
|
||||||
block.instructions.len()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
// Verify PHI is still there
|
|
||||||
if let Some(first_inst) = block.instructions.get(0) {
|
|
||||||
match first_inst {
|
|
||||||
MirInstruction::Phi { dst: phi_dst, .. } => {
|
|
||||||
if dbg {
|
|
||||||
get_global_ring0().log.debug(&format!(
|
|
||||||
"[DEBUG] Verified: First instruction is PHI dst=%{}",
|
|
||||||
phi_dst.0
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
if dbg {
|
|
||||||
get_global_ring0().log.warn(&format!(
|
|
||||||
"[DEBUG] ⚠️ WARNING: First instruction is NOT PHI! It's {:?}",
|
|
||||||
other
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
if dbg {
|
|
||||||
get_global_ring0()
|
|
||||||
.log
|
|
||||||
.warn(&format!("[DEBUG] ❌ Block {} not found!", block_id));
|
|
||||||
}
|
|
||||||
Err(format!("Block {} not found", block_id))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if dbg {
|
|
||||||
get_global_ring0()
|
|
||||||
.log
|
|
||||||
.warn("[DEBUG] ❌ No current function!");
|
|
||||||
}
|
|
||||||
Err("No current function".to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 30 F-2.1: LoopPhiOps 実装削除(loop_phi.rs 削除に伴う)
|
|
||||||
// LoopFormOps が SSOT として機能しているため、レガシー互換層は不要
|
|
||||||
|
|
||||||
// Implement LoopFormOps trait for LoopBuilder to support LoopFormBuilder integration
|
|
||||||
impl<'a> LoopFormOps for LoopBuilder<'a> {
|
|
||||||
fn new_value(&mut self) -> ValueId {
|
|
||||||
// CRITICAL: Must use MirFunction's next_value_id(), not MirBuilder's value_gen
|
|
||||||
// Otherwise we get SSA violations because the two counters diverge
|
|
||||||
let id = if let Some(ref mut func) = self.parent_builder.current_function {
|
|
||||||
let before = func.next_value_id;
|
|
||||||
let id = func.next_value_id();
|
|
||||||
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
|
||||||
get_global_ring0().log.debug(&format!(
|
|
||||||
"[LoopFormOps::new_value] fn='{}' counter: {} -> {}, allocated: {:?}",
|
|
||||||
func.signature.name, before, func.next_value_id, id
|
|
||||||
));
|
|
||||||
}
|
|
||||||
id
|
|
||||||
} else {
|
|
||||||
// Fallback (should never happen in practice)
|
|
||||||
let id = self.parent_builder.value_gen.next();
|
|
||||||
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
|
||||||
get_global_ring0().log.debug(&format!(
|
|
||||||
"[LoopFormOps::new_value] FALLBACK value_gen, allocated: {:?}",
|
|
||||||
id
|
|
||||||
));
|
|
||||||
}
|
|
||||||
id
|
|
||||||
};
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensure_counter_after(&mut self, max_id: u32) -> Result<(), String> {
|
|
||||||
if let Some(ref mut func) = self.parent_builder.current_function {
|
|
||||||
// 📦 Hotfix 1: Consider both parameter count and existing ValueIds
|
|
||||||
let param_count = func.signature.params.len() as u32;
|
|
||||||
let min_counter = param_count.max(max_id + 1);
|
|
||||||
|
|
||||||
if func.next_value_id < min_counter {
|
|
||||||
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
|
||||||
get_global_ring0().log.debug(&format!(
|
|
||||||
"[LoopFormOps::ensure_counter_after] fn='{}' params={}, max_id={}, adjusting counter {} -> {}",
|
|
||||||
func.signature.name, param_count, max_id, func.next_value_id, min_counter
|
|
||||||
));
|
|
||||||
}
|
|
||||||
func.next_value_id = min_counter;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err("No current function to adjust counter".to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn block_exists(&self, block: BasicBlockId) -> bool {
|
|
||||||
// 📦 Hotfix 2: Check if block exists in current function's CFG
|
|
||||||
if let Some(ref func) = self.parent_builder.current_function {
|
|
||||||
func.blocks.contains_key(&block)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_block_predecessors(
|
|
||||||
&self,
|
|
||||||
block: BasicBlockId,
|
|
||||||
) -> std::collections::BTreeSet<BasicBlockId> {
|
|
||||||
// 📦 Hotfix 6: Get actual CFG predecessors for PHI validation
|
|
||||||
// Phase 69-3: Changed to BTreeSet for determinism
|
|
||||||
if let Some(ref func) = self.parent_builder.current_function {
|
|
||||||
if let Some(bb) = func.blocks.get(&block) {
|
|
||||||
bb.predecessors.clone()
|
|
||||||
} else {
|
|
||||||
std::collections::BTreeSet::new() // Non-existent blocks have no predecessors
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
std::collections::BTreeSet::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Phase 26-A-4: ValueIdベースのパラメータ判定(GUARD Bug Prevention)
|
|
||||||
///
|
|
||||||
/// 旧実装(名前ベース)の問題点:
|
|
||||||
/// - ValueId(0) を「常に未初期化」と誤判定
|
|
||||||
/// - パラメータ s=ValueId(0) も弾いてしまうGUARDバグ
|
|
||||||
///
|
|
||||||
/// 新実装(型ベース)の利点:
|
|
||||||
/// - MirValueKindで型安全判定
|
|
||||||
/// - ValueId(0)でもParameter(0)なら正しく判定
|
|
||||||
fn is_parameter(&self, value_id: ValueId) -> bool {
|
|
||||||
// Phase 26-A-4: 型安全なパラメータ判定を使用
|
|
||||||
// parent_builder.is_value_parameter() は Phase 26-A-2 で実装済み
|
|
||||||
let is_param = self.parent_builder.is_value_parameter(value_id);
|
|
||||||
|
|
||||||
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
|
||||||
eprintln!(
|
|
||||||
"[is_parameter] ValueId({}) -> {} (kind = {:?})",
|
|
||||||
value_id.0,
|
|
||||||
is_param,
|
|
||||||
self.parent_builder.get_value_kind(value_id)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
is_param
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_current_block(&mut self, block: BasicBlockId) -> Result<(), String> {
|
|
||||||
self.parent_builder.start_new_block(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_copy(&mut self, dst: ValueId, src: ValueId) -> Result<(), String> {
|
|
||||||
self.parent_builder
|
|
||||||
.emit_instruction(MirInstruction::Copy { dst, src })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_jump(&mut self, target: BasicBlockId) -> Result<(), String> {
|
|
||||||
self.emit_jump(target)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_phi(
|
|
||||||
&mut self,
|
|
||||||
dst: ValueId,
|
|
||||||
inputs: Vec<(BasicBlockId, ValueId)>,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
self.emit_phi_at_block_start(self.current_block()?, dst, inputs)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_phi_inputs(
|
|
||||||
&mut self,
|
|
||||||
block: BasicBlockId,
|
|
||||||
phi_id: ValueId,
|
|
||||||
inputs: Vec<(BasicBlockId, ValueId)>,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
self.parent_builder
|
|
||||||
.update_phi_instruction(block, phi_id, inputs)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_var(&mut self, name: String, value: ValueId) {
|
|
||||||
self.parent_builder.variable_map.insert(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_variable_at_block(&self, name: &str, block: BasicBlockId) -> Option<ValueId> {
|
|
||||||
// Use the inherent method to avoid recursion
|
|
||||||
LoopBuilder::get_variable_at_block(self, name, block)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mir_function(&self) -> &crate::mir::MirFunction {
|
|
||||||
self.parent_builder
|
|
||||||
.current_function
|
|
||||||
.as_ref()
|
|
||||||
.expect("LoopBuilder requires current_function")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 26-E-3: PhiBuilderOps 委譲実装(has-a設計)
|
|
||||||
//
|
|
||||||
// **Design Rationale:**
|
|
||||||
// - PhiBuilderOps = 低レベル「PHI命令発行」道具箱
|
|
||||||
// - LoopFormOps = 高レベル「ループ構造構築」作業場
|
|
||||||
// - 関係: has-a(委譲) - LoopBuilder は両方のインターフェースを提供
|
|
||||||
//
|
|
||||||
// **実装方針:**
|
|
||||||
// - LoopFormOps の既存実装に委譲(重複なし)
|
|
||||||
// - HashSet → Vec 変換 + ソート(決定性保証)
|
|
||||||
// - emit_phi: block 引数差を吸収(current_block 設定)
|
|
||||||
impl<'a> crate::mir::phi_core::phi_builder_box::PhiBuilderOps for LoopBuilder<'a> {
|
|
||||||
fn new_value(&mut self) -> ValueId {
|
|
||||||
// 委譲: LoopFormOps の既存実装を使用
|
|
||||||
<Self as LoopFormOps>::new_value(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_phi(
|
|
||||||
&mut self,
|
|
||||||
block: BasicBlockId,
|
|
||||||
dst: ValueId,
|
|
||||||
inputs: Vec<(BasicBlockId, ValueId)>,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
// PhiBuilderOps: block 引数あり
|
|
||||||
// LoopFormOps: block 引数なし(current_block 使用)
|
|
||||||
// 差を吸収: current_block を設定してから LoopFormOps::emit_phi を呼ぶ
|
|
||||||
<Self as LoopFormOps>::set_current_block(self, block)?;
|
|
||||||
<Self as LoopFormOps>::emit_phi(self, dst, inputs)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_var(&mut self, name: String, value: ValueId) {
|
|
||||||
// 委譲: LoopFormOps の既存実装を使用
|
|
||||||
<Self as LoopFormOps>::update_var(self, name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_block_predecessors(&self, block: BasicBlockId) -> Vec<BasicBlockId> {
|
|
||||||
// LoopFormOps: HashSet<BasicBlockId> 返却
|
|
||||||
// PhiBuilderOps: Vec<BasicBlockId> 返却
|
|
||||||
// 変換: HashSet → Vec + ソート(決定性保証)
|
|
||||||
let pred_set = <Self as LoopFormOps>::get_block_predecessors(self, block);
|
|
||||||
let mut pred_vec: Vec<BasicBlockId> = pred_set.into_iter().collect();
|
|
||||||
pred_vec.sort_by_key(|bb| bb.0); // bb.0 = BasicBlockId の内部値
|
|
||||||
pred_vec
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_void(&mut self) -> ValueId {
|
|
||||||
// void 定数を発行
|
|
||||||
let void_id = <Self as LoopFormOps>::new_value(self);
|
|
||||||
let _ = self.emit_const(void_id, ConstValue::Void);
|
|
||||||
void_id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_current_block(&mut self, block: BasicBlockId) -> Result<(), String> {
|
|
||||||
// 委譲: LoopFormOps の既存実装を使用
|
|
||||||
<Self as LoopFormOps>::set_current_block(self, block)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn block_exists(&self, block: BasicBlockId) -> bool {
|
|
||||||
// 委譲: LoopFormOps の既存実装を使用
|
|
||||||
<Self as LoopFormOps>::block_exists(self, block)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
use super::{LoopBuilder, ValueId};
|
|
||||||
use crate::ast::ASTNode;
|
|
||||||
use crate::mir::utils::is_current_block_terminated;
|
|
||||||
use crate::mir::ConstValue;
|
|
||||||
|
|
||||||
impl<'a> LoopBuilder<'a> {
|
|
||||||
pub(super) fn build_statement(&mut self, stmt: ASTNode) -> Result<ValueId, String> {
|
|
||||||
// Preserve the originating span for loop-local control instructions (break/continue/phi).
|
|
||||||
self.parent_builder.current_span = stmt.span();
|
|
||||||
match stmt {
|
|
||||||
// Ensure nested bare blocks inside loops are lowered with loop-aware semantics
|
|
||||||
ASTNode::Program { statements, .. } => {
|
|
||||||
let mut last = None;
|
|
||||||
for s in statements.into_iter() {
|
|
||||||
last = Some(self.build_statement(s)?);
|
|
||||||
// フェーズS修正:統一終端検出ユーティリティ使用
|
|
||||||
if is_current_block_terminated(self.parent_builder)? {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(last.unwrap_or_else(|| {
|
|
||||||
let void_id = self.new_value();
|
|
||||||
// Emit a void const to keep SSA consistent when block is empty
|
|
||||||
let _ = self.emit_const(void_id, ConstValue::Void);
|
|
||||||
void_id
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
ASTNode::If {
|
|
||||||
condition,
|
|
||||||
then_body,
|
|
||||||
else_body,
|
|
||||||
..
|
|
||||||
} => self.lower_if_in_loop(*condition, then_body, else_body),
|
|
||||||
ASTNode::Break { .. } => self.do_break(),
|
|
||||||
ASTNode::Continue { .. } => self.do_continue(),
|
|
||||||
other => self.parent_builder.build_expression(other),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -16,7 +16,7 @@ pub mod instruction;
|
|||||||
pub mod instruction_introspection; // Introspection helpers for tests (instruction names)
|
pub mod instruction_introspection; // Introspection helpers for tests (instruction names)
|
||||||
pub mod instruction_kinds; // small kind-specific metadata (Const/BinOp)
|
pub mod instruction_kinds; // small kind-specific metadata (Const/BinOp)
|
||||||
pub mod loop_api; // Minimal LoopBuilder facade (adapter-ready)
|
pub mod loop_api; // Minimal LoopBuilder facade (adapter-ready)
|
||||||
pub mod loop_builder; // SSA loop construction with phi nodes
|
pub mod if_in_loop_phi; // Phase 187-2: Minimal if-in-loop PHI emitter (extracted from loop_builder)
|
||||||
pub mod naming; // Static box / entry naming rules(NamingBox)
|
pub mod naming; // Static box / entry naming rules(NamingBox)
|
||||||
pub mod optimizer;
|
pub mod optimizer;
|
||||||
pub mod ssot; // Shared helpers (SSOT) for instruction lowering
|
pub mod ssot; // Shared helpers (SSOT) for instruction lowering
|
||||||
|
|||||||
Reference in New Issue
Block a user