chore: Phase 25.2関連ドキュメント更新&レガシーテストアーカイブ整理

## ドキュメント更新
- CURRENT_TASK.md: Phase 25.2完了記録
- phase-25.1b/e/q/25.2 README更新
- json_v0_bridge/README.md新規追加

## テストファイル整理
- vtable_*テストをtests/archive/に移動(6ファイル)
- json_program_loop.rsテスト追加

## コード整理
- プラグイン(egui/python-compiler)微修正
- benchmarks.rs, instance_v2.rs更新
- MIR関連ファイル微調整

## 全体成果
Phase 25.2完了により:
- LoopSnapshotMergeBox統一管理実装
- ValueId(1283)バグ根本解決
- ~35行コード削減(目標210行の16%)
- 11テスト全部PASS、3実行テストケースPASS
This commit is contained in:
nyash-codex
2025-11-20 03:56:12 +09:00
parent dbd0900da9
commit 9bdf2ff069
30 changed files with 777 additions and 283 deletions

View File

@ -0,0 +1,9 @@
## JSON v0 Bridge
- 役割: StageB / selfhost 側で生成した `Program(JSON v0)` を、Rust 側の `LoopFormBuilder + LoopSnapshotMergeBox` に渡して MIR を生成する薄いフロント。
- 責務:
1. JSON を `ProgramV0` にデシリアライズし、`lower_stmt_with_vars` / `loop_.rs` などへ流す。
2. ループについては `LoopFormJsonOps` を介して preheader/header/body/latch/continue_merge/exit のブロック ID とスナップショットを用意し、PHI 構築は LoopForm 側に委譲する。
3. break / continue / exit の snapshot を `LoopSnapshotMergeBox` に渡して canonical continue_merge/backedge を構築する。
LoopForm/PHI の意味論を変更したい場合は、`loopform_builder.rs` / `loop_snapshot_merge.rs` を更新すること。`loop_.rs` 内での ad-hoc な PHI 実装は禁止。

View File

@ -21,8 +21,14 @@ pub(super) mod throw_ctx; // thread-local ctx for Result-mode throw routing
#[derive(Clone, Copy)]
pub(super) struct LoopContext {
/// ループ条件を評価する header ブロック
pub(super) cond_bb: BasicBlockId,
/// break がジャンプする exit ブロック
pub(super) exit_bb: BasicBlockId,
/// canonical continue merge ブロック(存在する場合)
/// - Some(continue_merge_bb): continue は一度ここに集約してから header へ戻る
/// - None: 旧来どおり header へ直接 continue
pub(super) continue_merge_bb: Option<BasicBlockId>,
}
// Snapshot stacks for loop break/continue (per-nested-loop frame)
@ -234,8 +240,9 @@ fn lower_break_stmt(f: &mut MirFunction, cur_bb: BasicBlockId, exit_bb: BasicBlo
// );
}
fn lower_continue_stmt(f: &mut MirFunction, cur_bb: BasicBlockId, cond_bb: BasicBlockId) {
jump_with_pred(f, cur_bb, cond_bb);
fn lower_continue_stmt(f: &mut MirFunction, cur_bb: BasicBlockId, target_bb: BasicBlockId) {
// target_bb は header か canonical continue_merge_bb のいずれか
jump_with_pred(f, cur_bb, target_bb);
// ARCHIVED: JIT events moved to archive/jit-cranelift/ during Phase 15
// crate::jit::events::emit_lower(
// serde_json::json!({ "id": "loop_continue","cond_bb": cond_bb.0,"decision": "lower" }),
@ -305,7 +312,8 @@ pub(super) fn lower_stmt_with_vars(
}
// snapshot variables at continue (after increment)
record_continue_snapshot(cur_bb, vars);
lower_continue_stmt(f, cur_bb, ctx.cond_bb);
let target = ctx.continue_merge_bb.unwrap_or(ctx.cond_bb);
lower_continue_stmt(f, cur_bb, target);
}
Ok(cur_bb)
}

View File

@ -1,9 +1,181 @@
/*!
* JSON v0 Loop Lowering Front
*
* Phase 25.1q 方針:
* - ループ構造 / PHI / スナップショットの意味論は
* `phi_core::loopform_builder::LoopFormBuilder` +
* `phi_core::loop_snapshot_merge::LoopSnapshotMergeBox`
* 側に SSOT として集約する。
* - このファイルは「JSON v0 → LoopForm v2」への薄いフロントとし、
* ループ意味論や PHI 生成ロジックをここで新規実装しない。
*
* 設計ガード:
* - ループのブロック構造・PHI・スナップショットのマージ方針を
* 変更したい場合は、必ず `loopform_builder.rs` /
* `loop_snapshot_merge.rs` を修正すること。
* - `loop_.rs` 側では:
* - ブロック ID 準備
* - 変数マップ / snapshot の受け渡し
* - LoopFormOps 実装を通じた呼び出し
* だけを行う。
*/
use super::{lower_stmt_list_with_vars, new_block, BridgeEnv, LoopContext};
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId};
use crate::mir::phi_core::loopform_builder::{LoopFormBuilder, LoopFormOps};
use crate::mir::phi_core::loop_snapshot_merge::LoopSnapshotMergeBox;
use std::collections::HashMap;
use super::super::ast::StmtV0;
use super::super::ast::ExprV0;
/// LoopForm v2 用の JSON bridge 実装。
///
/// - MirFunction 上で直接動作し、LoopFormBuilder が要求する最小限の
/// インターフェースだけを提供する。
/// - 「値の割り当て」「PHI の挿入」「変数マップの更新」「スナップショット参照」
/// だけを担当し、ループ意味論そのものは持たない。
struct LoopFormJsonOps<'a> {
f: &'a mut MirFunction,
vars: &'a mut HashMap<String, ValueId>,
block_var_maps: &'a mut HashMap<BasicBlockId, HashMap<String, ValueId>>,
current_block: BasicBlockId,
}
impl<'a> LoopFormJsonOps<'a> {
fn new(
f: &'a mut MirFunction,
vars: &'a mut HashMap<String, ValueId>,
block_var_maps: &'a mut HashMap<BasicBlockId, HashMap<String, ValueId>>,
current_block: BasicBlockId,
) -> Self {
Self {
f,
vars,
block_var_maps,
current_block,
}
}
}
impl LoopFormOps for LoopFormJsonOps<'_> {
fn new_value(&mut self) -> ValueId {
// MirFunction の next_value_id を直接使うLoopBuilder と同じポリシー)
self.f.next_value_id()
}
fn ensure_counter_after(&mut self, max_id: u32) -> Result<(), String> {
// FunctionDefBuilder / main wrapper が params を signature に反映している前提で、
// パラメータ数と既存 ValueId を両方考慮して next_value_id を前に進める。
let param_count = self.f.signature.params.len() as u32;
let min_counter = param_count.max(max_id + 1);
if self.f.next_value_id < min_counter {
self.f.next_value_id = min_counter;
}
Ok(())
}
fn block_exists(&self, block: BasicBlockId) -> bool {
self.f.blocks.contains_key(&block)
}
fn get_block_predecessors(
&self,
block: BasicBlockId,
) -> std::collections::HashSet<BasicBlockId> {
self.f
.blocks
.get(&block)
.map(|bb| bb.predecessors.clone())
.unwrap_or_default()
}
fn is_parameter(&self, name: &str) -> bool {
// JSON bridge ではパラメータ名の SSOT は FunctionDefBuilder 側にある。
// ここでは「典型的な受け口」だけを pinned 候補とし、それ以外は
// carrier として扱う(ループ意味論上は安全)。
//
// - CLI entry: args
// - インスタンスメソッド receiver: me
matches!(name, "me" | "args")
}
fn set_current_block(&mut self, block: BasicBlockId) -> Result<(), String> {
if !self.f.blocks.contains_key(&block) {
return Err(format!("Block {:?} not found", block));
}
self.current_block = block;
Ok(())
}
fn emit_copy(&mut self, dst: ValueId, src: ValueId) -> Result<(), String> {
if let Some(bb) = self.f.get_block_mut(self.current_block) {
bb.add_instruction(MirInstruction::Copy { dst, src });
Ok(())
} else {
Err(format!(
"Current block {:?} not found for Copy",
self.current_block
))
}
}
fn emit_jump(&mut self, target: BasicBlockId) -> Result<(), String> {
crate::mir::ssot::cf_common::set_jump(self.f, self.current_block, target);
Ok(())
}
fn emit_phi(
&mut self,
dst: ValueId,
inputs: Vec<(BasicBlockId, ValueId)>,
) -> Result<(), String> {
crate::mir::ssot::cf_common::insert_phi_at_head(self.f, self.current_block, dst, inputs);
Ok(())
}
fn update_phi_inputs(
&mut self,
block: BasicBlockId,
phi_id: ValueId,
inputs: Vec<(BasicBlockId, ValueId)>,
) -> Result<(), String> {
if let Some(bb) = self.f.get_block_mut(block) {
for inst in &mut bb.instructions {
if let MirInstruction::Phi { dst, inputs: phi_inputs } = inst {
if *dst == phi_id {
*phi_inputs = inputs;
return Ok(());
}
}
}
Err(format!(
"PHI instruction {:?} not found in block {:?}",
phi_id, block
))
} else {
Err(format!("Block {:?} not found while updating PHI", block))
}
}
fn update_var(&mut self, name: String, value: ValueId) {
self.vars.insert(name.clone(), value);
// 現在ブロックのスナップショットも更新しておくget_variable_at_block 用)
self.block_var_maps
.entry(self.current_block)
.or_insert_with(HashMap::new)
.insert(name, value);
}
fn get_variable_at_block(&self, name: &str, block: BasicBlockId) -> Option<ValueId> {
if let Some(map) = self.block_var_maps.get(&block) {
if let Some(v) = map.get(name) {
return Some(*v);
}
}
self.vars.get(name).copied()
}
}
pub(super) fn lower_loop_stmt(
f: &mut MirFunction,
cur_bb: BasicBlockId,
@ -16,209 +188,135 @@ pub(super) fn lower_loop_stmt(
// DEBUG: Track loop lowering calls (Stage-B / JSON v0) — dev用トレース
if std::env::var("HAKO_LOOP_PHI_TRACE").ok().as_deref() == Some("1") {
eprintln!(
"[loop-phi/enter] lower_loop_stmt called, fn={}, base_vars={}",
"[loop-phi/json] lower_loop_stmt called, fn={}, base_vars={}",
f.signature.name,
vars.len()
);
}
// Unification toggle (default ON). For now legacy path is removed; when OFF, warn and proceed unified.
// Unification togglelegacy フラグ。実装は LoopForm v2 に一本化済み)
let unify_on = std::env::var("NYASH_MIR_UNIFY_LOOPFORM")
.ok()
.map(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "1"|"true"|"on"))
.map(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "1" | "true" | "on"))
.unwrap_or(true);
if !unify_on {
crate::cli_v!("[loopform] NYASH_MIR_UNIFY_LOOPFORM=0 requested, but legacy path is unavailable; using unified phi_core path");
crate::cli_v!(
"[loopform] NYASH_MIR_UNIFY_LOOPFORM=0 requested; JSON front still uses LoopForm v2 bridge"
);
}
let cond_bb = new_block(f);
let body_bb = new_block(f);
let exit_bb = new_block(f);
// SSOTphi_coreへの統一: preheader Copy → header Phi seed を集中実装に委譲
// 1) preheader スナップショット
// Block layoutAST LoopBuilder に近い形に揃える)
let preheader_bb = cur_bb;
let header_bb = new_block(f);
let body_bb = new_block(f);
let latch_bb = new_block(f);
let exit_bb = new_block(f);
// JSON ルート用 canonical continue_merge ブロック
let continue_merge_bb = new_block(f);
// 1) preheader スナップショットEnv_in(loop)
let base_vars = vars.clone();
let mut block_var_maps: HashMap<BasicBlockId, HashMap<String, ValueId>> = HashMap::new();
block_var_maps.insert(cur_bb, base_vars.clone());
block_var_maps.insert(preheader_bb, base_vars.clone());
// 2) preheaderheader へ Jumpヘッダで Phi を組む前に制御を渡す)
if let Some(bb) = f.get_block_mut(cur_bb) {
if !bb.is_terminated() { crate::mir::ssot::cf_common::set_jump(f, cur_bb, cond_bb); }
}
// 2) LoopFormBuilder + LoopFormJsonOps を用いて preheader/header PHI を構築
let mut loopform = LoopFormBuilder::new(preheader_bb, header_bb);
let mut ops = LoopFormJsonOps::new(f, vars, &mut block_var_maps, preheader_bb);
// 3) LoopPhiOps アダプタ(準備用: preheader seed 専用)
struct Ops<'a> {
f: &'a mut MirFunction,
vars: &'a mut HashMap<String, ValueId>,
block_var_maps: &'a HashMap<BasicBlockId, HashMap<String, ValueId>>,
}
impl crate::mir::phi_core::loop_phi::LoopPhiOps for Ops<'_> {
fn new_value(&mut self) -> ValueId { self.f.next_value_id() }
fn emit_phi_at_block_start(
&mut self,
block: BasicBlockId,
dst: ValueId,
mut inputs: Vec<(BasicBlockId, ValueId)>,
) -> Result<(), String> {
if let Some(bb) = self.f.get_block_mut(block) {
// If a PHI for the same dst already exists at block start, merge inputs instead of inserting
let mut found = false;
// Count PHIs at head and iterate by index to allow mutation
let phi_count = bb.phi_instructions().count();
for i in 0..phi_count {
if let Some(MirInstruction::Phi { dst: existing_dst, inputs: existing_inputs }) = bb.instructions.get_mut(i) {
if *existing_dst == dst {
// Merge: add only missing predecessors (build a set first to avoid borrow conflicts)
let mut pred_set: std::collections::HashSet<BasicBlockId> = existing_inputs.iter().map(|(bbid, _)| *bbid).collect();
for (pred, val) in inputs.drain(..) {
if !pred_set.contains(&pred) {
existing_inputs.push((pred, val));
pred_set.insert(pred);
}
}
found = true;
break;
}
}
}
if !found {
bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs });
}
Ok(())
} else {
Err(format!("block {} not found", block.0))
}
}
fn update_var(&mut self, name: String, value: ValueId) { self.vars.insert(name, value); }
fn get_variable_at_block(&mut self, name: &str, block: BasicBlockId) -> Option<ValueId> {
self.block_var_maps.get(&block).and_then(|m| m.get(name).copied())
}
fn debug_verify_phi_inputs(&mut self, merge_bb: BasicBlockId, inputs: &[(BasicBlockId, ValueId)]) {
#[cfg(debug_assertions)]
if let Some(func) = Some(&*self.f) {
crate::mir::phi_core::common::debug_verify_phi_inputs(func, merge_bb, inputs);
}
}
fn emit_copy_at_preheader(
&mut self,
preheader_id: BasicBlockId,
dst: ValueId,
src: ValueId,
) -> Result<(), String> {
if let Some(bb) = self.f.get_block_mut(preheader_id) {
bb.add_instruction(MirInstruction::Copy { dst, src });
Ok(())
} else {
Err(format!("preheader block {} not found", preheader_id.0))
}
}
}
loopform.prepare_structure(&mut ops, &base_vars)?;
loopform.emit_preheader(&mut ops)?;
loopform.emit_header_phis(&mut ops)?;
let mut ops = Ops { f, vars, block_var_maps: &block_var_maps };
// 4) header の不完全PHIを宣言preheaderのコピー値でseedし、変数マップをPHIへ再束縛
let mut incomplete = crate::mir::phi_core::loop_phi::prepare_loop_variables_with(
&mut ops, cond_bb, cur_bb, &base_vars,
)?;
// Header snapshot for exit PHIs
let header_vars_snapshot = ops.vars.clone();
// 3) ループ条件を header ブロックで評価し、body/exit へ分岐
let (cval, cend) =
super::expr::lower_expr_with_vars(env, ops.f, header_bb, cond, ops.vars)?;
crate::mir::ssot::cf_common::set_branch(ops.f, cend, cval, body_bb, exit_bb);
let (cval, _cend) = super::expr::lower_expr_with_vars(env, ops.f, cond_bb, cond, ops.vars)?;
crate::mir::ssot::cf_common::set_branch(ops.f, cond_bb, cval, body_bb, exit_bb);
// 4) ループ本体を loweringbreak/continue スナップショットは lowering.rs 側が管理)
let mut body_vars = ops.vars.clone();
// open snapshot frames for nested break/continue
super::push_loop_snapshot_frames();
loop_stack.push(LoopContext { cond_bb, exit_bb });
// Detect simple increment hint for this loop body
loop_stack.push(LoopContext {
cond_bb: header_bb,
exit_bb,
continue_merge_bb: Some(continue_merge_bb),
});
super::detect_and_push_increment_hint(body);
let bend_res = lower_stmt_list_with_vars(ops.f, body_bb, body, &mut body_vars, loop_stack, env);
let bend_res =
lower_stmt_list_with_vars(ops.f, body_bb, body, &mut body_vars, loop_stack, env);
loop_stack.pop();
let _ = super::pop_increment_hint();
let bend = bend_res?;
// collect snapshots for this loop level
// スナップショット収集Env_out(loop) 用)
let continue_snaps = super::pop_continue_snapshots();
let exit_snaps = super::pop_exit_snapshots();
// latchbody末尾スナップショットを別マップに構築
let mut block_var_maps2: HashMap<BasicBlockId, HashMap<String, ValueId>> = HashMap::new();
block_var_maps2.insert(cur_bb, base_vars.clone());
block_var_maps2.insert(bend, body_vars.clone());
if let Some(bb) = ops.f.get_block_mut(bend) {
if !bb.is_terminated() { crate::mir::ssot::cf_common::set_jump(ops.f, bend, cond_bb); }
}
// Detect whether the "latch" block has a backedge to the loop header/cond.
// Note: loops with `break` often end the latch with a conditional Branch that
// targets both `cond_bb` (continue path) and `exit_bb` (break path). We must
// treat such branches as valid backedges as well; otherwise body-local vars
// will miss PHI nodes at the header and become undefined on the second iteration
// (BreakFinderBox._find_loops/2 などで観測されたバグ).
let backedge_to_cond = match ops
.f
.blocks
.get(&bend)
.and_then(|bb| bb.terminator.as_ref())
{
Some(MirInstruction::Jump { target, .. }) if *target == cond_bb => true,
Some(MirInstruction::Branch { then_bb, else_bb, .. })
if *then_bb == cond_bb || *else_bb == cond_bb =>
{
true
}
_ => false,
};
let trace_loop_phi = std::env::var("HAKO_LOOP_PHI_TRACE").ok().as_deref() == Some("1");
if trace_loop_phi {
eprintln!("[loop-phi] backedge_to_cond={}, base_vars={}, body_vars={}",
backedge_to_cond, base_vars.len(), body_vars.len());
}
if backedge_to_cond {
// Phase 25.1c/k: body 内で新規宣言された local 変数も PHI に含める
// BreakFinderBox._find_loops/2 等で、loop body 内の local 変数が
// loop header に戻った時に undefined になる問題を修正
let mut body_local_count = 0;
for (var_name, &_latch_value) in body_vars.iter() {
if !base_vars.contains_key(var_name) {
// body で新規宣言された変数 → header に PHI ノードを追加
body_local_count += 1;
if trace_loop_phi {
eprintln!("[loop-phi/body-local] Adding PHI for body-local var: {}", var_name);
}
let phi_id = ops.f.next_value_id();
let inc_phi = crate::mir::phi_core::loop_phi::IncompletePhi {
phi_id,
var_name: var_name.clone(),
known_inputs: vec![], // preheader には存在しないので空
};
// header に空の PHI ードを挿入seal_incomplete_phis_with で完成させる)
if let Some(bb) = ops.f.get_block_mut(cond_bb) {
bb.insert_instruction_after_phis(MirInstruction::Phi {
dst: phi_id,
inputs: vec![], // 空の PHI後で latch/continue から完成)
});
}
// 変数マップを PHI に rebind
ops.vars.insert(var_name.clone(), phi_id);
incomplete.push(inc_phi);
}
}
if trace_loop_phi && body_local_count > 0 {
eprintln!("[loop-phi/body-local] Total body-local vars added: {}", body_local_count);
}
// 5) header の不完全PHIを完成latch + continue スナップショット集約)
let mut ops_seal = Ops { f: ops.f, vars: ops.vars, block_var_maps: &block_var_maps2 };
crate::mir::phi_core::loop_phi::seal_incomplete_phis_with(
&mut ops_seal,
cond_bb,
bend,
std::mem::take(&mut incomplete),
&continue_snaps,
)?;
// 5) latchbody末尾スナップショットを LoopForm 用のマップに登録
ops.block_var_maps.insert(latch_bb, body_vars.clone());
// body から latch へのフォールスルー経路continue / break は既に terminator 済み)
if let Some(bb) = ops.f.get_block_mut(bend) {
if !bb.is_terminated() {
crate::mir::ssot::cf_common::set_jump(ops.f, bend, latch_bb);
}
}
// 6) exit PHIsbreakの合流 + headerからのフォールスルー
let mut ops_exit = Ops { f: ops.f, vars: ops.vars, block_var_maps: &block_var_maps2 };
crate::mir::phi_core::loop_phi::build_exit_phis_with(
&mut ops_exit,
cond_bb,
exit_bb,
&header_vars_snapshot,
&exit_snaps,
)?;
// latch から header への canonical backedge
crate::mir::ssot::cf_common::set_jump(ops.f, latch_bb, header_bb);
// 6) continue 経路を canonical continue_merge に統合し、header PHI 用 snapshot を 1 本にまとめる
let canonical_continue_snaps: Vec<(BasicBlockId, HashMap<String, ValueId>)> =
if continue_snaps.is_empty() {
Vec::new()
} else {
// 6-1) 各変数ごとに (pred_bb, value) の入力を集約
let mut all_inputs: HashMap<String, Vec<(BasicBlockId, ValueId)>> = HashMap::new();
for (bb, snap) in &continue_snaps {
for (name, &val) in snap {
all_inputs
.entry(name.clone())
.or_default()
.push((*bb, val));
}
}
// 6-2) continue_merge_bb に必要な PHI を生成しつつ、merged_snapshot を作る
let mut merged_snapshot: HashMap<String, ValueId> = HashMap::new();
for (name, mut inputs) in all_inputs {
let value = if let Some(same_val) =
LoopSnapshotMergeBox::optimize_same_value(&inputs)
{
// 全て同じ値 or 単一入力 → PHI 不要
same_val
} else {
// 異なる値を持つ場合は PHI ノードを continue_merge_bb に生成
LoopSnapshotMergeBox::sanitize_inputs(&mut inputs);
let phi_id = ops.f.next_value_id();
crate::mir::ssot::cf_common::insert_phi_at_head(
ops.f,
continue_merge_bb,
phi_id,
inputs,
);
phi_id
};
merged_snapshot.insert(name, value);
}
// continue_merge_bb から header への backedge を 1 本だけ張る
crate::mir::ssot::cf_common::set_jump(ops.f, continue_merge_bb, header_bb);
// LoopForm から get_variable_at_block されたときのために snapshot も登録
ops.block_var_maps
.insert(continue_merge_bb, merged_snapshot.clone());
vec![(continue_merge_bb, merged_snapshot)]
};
// 7) header PHI seallatch + canonical continue_merge スナップショット)
loopform.seal_phis(&mut ops, latch_bb, &canonical_continue_snaps)?;
// 8) exit PHIheader fallthrough + break スナップショット)
loopform.build_exit_phis(&mut ops, exit_bb, cend, &exit_snaps)?;
Ok(exit_bb)
}