fix(loop/loopform): 専用preheader作成でValueId定義エラー解消 (WIP)

**問題**: NYASH_LOOPFORM_PHI_V2=1 でValueId未定義エラー発生
- emit_preheader()が既存blockに命令追加 → forward reference
- if-then内のloopでpreheader_id = current_block()が誤動作

**解決策**: LLVM canonical loop form準拠
- 専用preheaderブロック作成 (before_loop → preheader → header)
- 変数スナップショットをnew_block()前に取得
- emit_jump(preheader)で明示的分離

**成果**:
 ValueId定義エラー完全解消 (ValueId(10) etc.)
 詳細デバッグ出力追加 (variable_map追跡)
 新しい型エラー発生 (要調査: variable_map不整合)

**デバッグ出力**:
- before_loop_id + variable_map size
- Block IDs (preheader/header/body/latch/exit)
- 変数ごとのparam判定 + iteration count

**次のステップ**: variable_map変更点特定
- before_loop: args=ValueId(0)
- prepare_structure: args=ValueId(1) ← なぜ変わる?

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-17 05:48:03 +09:00
parent c459135238
commit 47071f2755
2 changed files with 28 additions and 8 deletions

View File

@ -237,7 +237,7 @@ Update (2025-11-16 — Phase 25.1b: selfhost builder multi-carrier & BoxTypeInsp
- さらに Rust VM 側の `MirInterpreter::reg_load` に開発用の追加情報を付けたことで、`Invalid value: use of undefined value ValueId(N)` が発生した際に `fn` / `last_block` / `last_inst` がエラーメッセージに含まれるようになり、StageB Main.main 内の `ParserBox.length()` 呼び出しが recv 未定義で落ちていることを特定できるようになったNYASH_VM_TRACE/NYASH_VM_TRACE_EXEC 未設定時でも場所が分かる)。
- なお、StageB を selfhost CLI サンプルに対して実行した際に現時点で見えている残存課題は次の 2 点:
- 1) `if args { ... }` まわりの truthy 判定ArrayBox を boolean 条件に使っている部分)の扱いに起因する型エラーであり、これは SSA ではなく「条件式の型truthy 規約」をどう定義するかという別問題として扱うPhase 25.1c 以降の型システム整理タスクで扱う想定)。
- 2) Rust VM 実行時に `❌ VM error: Invalid value: use of undefined value ValueId(17)` が発生しており、拡張したエラーメッセージ(初期段階では `fn=Main.main`、StageB 箱分割後は `fn=StageBArgsBox.resolve_src/1` として報告から「StageB パスにおける `ParserBox.length()` 呼び出しで Method recvValueId(17)) が適切に定義されていない」ことが判明している。Task先生の調査では、loop header/body の PHI 生成まわりで pinned スロットに対して循環依存する入力(例: `bb6` 内で `%17 = phi[%14,bb3,...]` かつ `%14` 自体同じ `bb6` の後続で定義されるがあり、Use-before-def ではなく「PHI 入力の cycle」に近い構造バグであることが分かっている。これは verifier (`NYASH_VM_VERIFY_MIR=1`) が現状まだ検出していない「Callee.receiver 側PHI 配線」の問題であり、Phase 25.1c の StageB / LoopBuilder / LocalSSA 整理タスクの中で (a) MethodCall の recv に対しても `Undefined value` 検査を掛ける、(b) loop_phi / pinned 変数の PHI 配線を修正して循環入力を構造的に防ぐ、という方針で扱う想定。
- 2) Rust VM 実行時に `❌ VM error: Invalid value: use of undefined value ValueId(17)` が発生しており、拡張したエラーメッセージ(初期段階では `fn=Main.main`、StageB 箱分割後は `fn=StageBArgsBox.resolve_src/1` として報告から「StageB パスにおける `ParserBox.length()` 呼び出しで Method recvValueId(17)) が適切に定義されていない」ことが判明している。Task先生Claude code 君の調査では、loop header/body の PHI 生成まわりで pinned スロットに対して循環依存する入力(例: あるループで header から body への PHI が `%17 = phi[%14,bb3,...]` を持つ一方で `%14` 自体同じ `bb6` の後続で定義される)になっているケースがあり、Use-before-def というより「PHI 入力の cycle」に近い構造バグであることが分かっている。また、LoopForm v2 の prototype では preheader 専用ブロックを導入し、`prepare_structure` 時点で `current_vars` における `args` が preheader 手前では `ValueId(0)`(関数パラメータの本来の値)、`prepare_structure` 呼び出し直前には `ValueId(1)` に変わってしまっていることも確認されており、`new_block` / `emit_jump` / variable_map 更新のどこかで pinned/キャリア変数の初期値がずれている疑いが強い。この一連の問題は verifier (`NYASH_VM_VERIFY_MIR=1`) が現状まだ検出していない「Callee.receiver 側PHI 配線」の問題であり、Phase 25.1c の StageB / LoopBuilder / LocalSSA 整理タスクの中で (a) MethodCall の recv に対しても `Undefined value` 検査を掛ける、(b) loop_phi / LoopFormBuilder / pinned 変数の PHI 配線を修正して循環入力を構造的に防ぐ、(c) preheader 専用ブロックと variable_map スナップショットを使った LoopForm v2 の統合方針を検討する、という流れで扱う想定。
- Next tasks (Phase 25.1b → 25.1c handoff / Codex):
1. Rust 層 Call/ExternCall 契約のドキュメント固定Step 4.1
- `src/mir/builder/builder_calls.rs` / `src/backend/mir_interpreter/handlers/{calls,externs,extern_provider}.rs` / `src/runtime/plugin_loader_v2/enabled/extern_functions.rs` をベースに、「MethodCall/ExternCall/hostbridge.extern_invoke/ env.codegen/env.mirbuilder」の SSOT を Phase 25.1b README に記録(実施済み)。

View File

@ -137,26 +137,46 @@ impl<'a> LoopBuilder<'a> {
condition: ASTNode,
body: Vec<ASTNode>,
) -> Result<ValueId, String> {
// Create loop structure blocks
let preheader_id = self.current_block()?;
// 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();
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
eprintln!("[loopform] before_loop_id={:?}, variable_map size={}",
before_loop_id, current_vars.len());
for (name, value) in &current_vars {
eprintln!(" {} -> {:?}", name, value);
}
}
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();
// Jump from current block to preheader
self.emit_jump(preheader_id)?;
// Initialize LoopFormBuilder with preheader and header blocks
let mut loopform = LoopFormBuilder::new(preheader_id, header_id);
// Capture current variable map snapshot at preheader
let current_vars = self.get_current_variable_map();
// Pass 1: Prepare structure (allocate all ValueIds upfront)
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
eprintln!("[loopform] variable_map at loop entry:");
eprintln!("[loopform] Block IDs: preheader={:?}, header={:?}, body={:?}, latch={:?}, exit={:?}",
preheader_id, header_id, body_id, latch_id, exit_id);
eprintln!("[loopform] variable_map at loop entry (size={}):", current_vars.len());
let mut loop_count = 0;
for (name, value) in &current_vars {
loop_count += 1;
eprintln!(" [{}] {} -> {:?}", loop_count, name, value);
let is_param = self.is_parameter(name);
eprintln!(" {} -> {:?} (param={})", name, value, is_param);
eprintln!(" param={}", is_param);
}
eprintln!("[loopform] iterated {} times", loop_count);
}
loopform.prepare_structure(self, &current_vars)?;