From 3a8263392438c84d0cebefa19d061f53992b6185 Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Thu, 20 Nov 2025 06:38:43 +0900 Subject: [PATCH] =?UTF-8?q?refactor(funcscanner):=20Region+next=5Fi=20?= =?UTF-8?q?=E3=83=91=E3=82=BF=E3=83=BC=E3=83=B3=E7=B5=B1=E4=B8=80=20&=20SS?= =?UTF-8?q?A=20=E3=83=86=E3=82=B9=E3=83=88=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **FuncScanner .hako 側改善**: - scan_all_boxes を Region + next_i 形式に統一(continue 多発による SSA/PHI 複雑さ削減) - インデント修正(タブ→スペース統一) - デバッグ print 削除 **SSA テスト追加**: - lang/src/compiler/tests/funcscanner_scan_methods_min.hako - src/tests/mir_funcscanner_ssa.rs (scan_methods & fib_min SSA デバッグテスト) **Phase 25.3 ドキュメント**: - docs/development/roadmap/phases/phase-25.3-funcscanner/ 追加 **関連**: Phase 25.3 FuncScanner 箱化準備作業 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CURRENT_TASK.md | 18 +- .../phases/phase-25.3-funcscanner/README.md | 151 ++++++++++++++++ lang/src/compiler/entry/func_scanner.hako | 161 +++++++++--------- .../tests/funcscanner_scan_methods_min.hako | 43 +++++ src/mir/builder.rs | 2 +- src/mir/builder/calls/build.rs | 61 +++++++ src/mir/builder/calls/unified_emitter.rs | 2 +- src/mir/builder/lifecycle.rs | 9 +- src/mir/builder/method_call_handlers.rs | 101 +++++++---- src/mir/instruction/methods.rs | 11 +- src/mir/instruction_kinds/mod.rs | 14 +- src/mir/verification.rs | 7 + src/mir/verification/ssa.rs | 4 + src/tests/mir_funcscanner_ssa.rs | 99 +++++++++++ src/tests/mod.rs | 1 + 15 files changed, 558 insertions(+), 126 deletions(-) create mode 100644 docs/development/roadmap/phases/phase-25.3-funcscanner/README.md create mode 100644 lang/src/compiler/tests/funcscanner_scan_methods_min.hako create mode 100644 src/tests/mir_funcscanner_ssa.rs diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index e57cf700..21947be6 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -268,12 +268,13 @@ 次にどの箱から攻めるか決めたら、ここに箇条書きで足していこうね。 -## 2-? Stage‑B FuncScanner / LoopSnapshotMergeBox(in progress) +## 2-? Stage‑B FuncScanner / LoopSnapshotMergeBox / MIR ロガー(in progress) - StageBFuncScannerBox を `compiler_stageb.hako` 内に追加し、StageBDriverBox.main からは FuncScannerBox ではなくこちらを呼ぶ構成にしている。 - VM 側の Undefined value(BreakFinderBox._find_loops/2)は LoopForm v2 / continue + PHI 修正で解消済み。 - FuncScannerBox.scan_all_boxes/1 については、Phase 25.2 で `LoopSnapshotMergeBox` を導入し、continue/break/exit スナップショットのマージを一元化したことで - 13 個の continue を含む複雑ループでも Undefined Value が出ないところまで到達した。 + 13 個の continue を含む複雑ループでも Undefined Value が出ないところまで到達しており、 + さらに `lang/src/compiler/tests/funcscanner_fib_min.hako` を使った最小ハーネスでも `NYASH_VM_VERIFY_MIR=1` で Undefined Value が再現しないところまで確認済み。 - LoopSnapshotMergeBox 実装: `src/mir/phi_core/loop_snapshot_merge.rs` - `merge_continue_for_header` / `merge_exit` / `optimize_same_value` / `sanitize_inputs` など、ヘッダ/exit PHI 用の入力統合ロジックを提供。 - LoopBuilder / LoopFormBuilder は、この箱を経由して continue_merge / exit PHI を構成するようにリファクタ済み。 @@ -285,7 +286,18 @@ - body-local 変数を含むループ: result=6, i=3 ✅(exit PHI で body-local を正しく統合) - StageBFuncScannerBox.scan_all_boxes(src) は依然として fib サンプルに対して defs=[](Program(JSON) に `TestBox.fib` が載っていない)という問題が残っているため、 次の担当者は StageBFuncScannerBox.scan_all_boxes/1 と FuncScannerBox.scan_all_boxes/1 の MIR を比較しつつ、 - Stage‑B body 抽出 → FuncScanner → defs 注入の導線自体を見直してほしい(LoopForm/PHI/スナップショット側は LoopSnapshotMergeBox でほぼ安定化済み)。 + Stage‑B body 抽出 → StageBFuncScannerBox.scan_all_boxes → Program(JSON v0).defs 注入の導線自体を見直してほしい(LoopForm/PHI/スナップショット側は LoopSnapshotMergeBox で安定化済み)。 +- Phase 25.3 では、FuncScanner / Stage‑B ラインの観測専用として `.hako` から直接 MIR ログを埋め込める `__mir__` 構文を導入した。 + - 構文: `__mir__.log("label", v1, v2, ...)` / `__mir__.mark("label")` + - lowering: `MirInstruction::DebugLog { message: "label", values: [v1, v2, ...] }` に変換される(LoopForm/PHI には影響しない)。 + - 実行: `NYASH_MIR_DEBUG_LOG=1` のときだけ `[MIR-LOG] label: %id=value ...` を VM 側で出力し、それ以外は no-op。 + - 実装ポイント: `src/mir/builder/calls/build.rs` の `build_method_call_impl` で `__mir__` receiver を検出し、`try_build_mir_debug_call` で `DebugLog` 命令に directly lowering している。 + - これにより、FuncScannerBox.scan_all_boxes や StageBFuncScannerBox.scan_all_boxes のループ頭や `box` 検出位置にラベルを打ち、 + VM 実行ログと合わせて「どの経路で未初期化の値が残っているか」を .hako 側から追いやすくなった。 + - 併せて `MirBuilder::handle_me_method_call` を `MeCallPolicyBox` で箱化し、me-call のフォールバックを静的メソッド降下に統一した。 + - `Box.method/Arity` lowered 関数が存在する場合は従来どおり `me` を先頭引数にした Global call。 + - 存在しない場合は `handle_static_method_call(cls, method, arguments)` に委譲し、static helper(FuncScannerBox._parse_params / _strip_comments など)呼び出しとして扱う。 + - これにより static box 文脈で「実体のない me を receiver にした Method call」が生成される経路が閉じられ、FuncScannerBox._scan_methods/4 系の UndefinedValue は Rust 側で構造的に防止されている。 ## 3. Static Box フィールド仕様ドキュメント(完了) diff --git a/docs/development/roadmap/phases/phase-25.3-funcscanner/README.md b/docs/development/roadmap/phases/phase-25.3-funcscanner/README.md new file mode 100644 index 00000000..51d80eeb --- /dev/null +++ b/docs/development/roadmap/phases/phase-25.3-funcscanner/README.md @@ -0,0 +1,151 @@ +# Phase 25.3 — FuncScanner / Stage‑B defs 安定化 + +## スコープ / ゴール + +- 対象レイヤ + - `.hako` 側: + - `lang/src/compiler/entry/func_scanner.hako` (`FuncScannerBox`) + - `lang/src/compiler/entry/compiler_stageb.hako` (`StageBFuncScannerBox`, `StageBDriverBox`) + - 必要に応じて `lang/src/compiler/tests/funcscanner_fib_min.hako` などのテスト用ハーネス + - Rust 側: + - 既存の LoopForm v2 / LoopSnapshotMergeBox / JSON front は「完成済みの土台」として利用するだけで、原則ここでは触らない。 + +- ゴール + - Rust VM 検証付きで FuncScanner を安定させる: + - `NYASH_VM_VERIFY_MIR=1` で `lang/src/compiler/tests/funcscanner_fib_min.hako` を実行したときに、 + - `FuncScannerBox.scan_all_boxes/1` で Undefined value が発生しないこと。 + - fib 風ソースに対して `defs` に `TestBox.fib` / `Main.main` が含まれること(箱レベルの振る舞いが安定)。 + - Stage‑B 側からも同じ結果が得られる: + - `tools/smokes/v2/profiles/quick/core/phase251/stageb_fib_program_defs_canary_vm.sh` を実行すると、 + - `defs` に `TestBox.fib` が存在し、 + - その `body` に `Loop` ノードが含まれていること。 + - Loop/PHI の意味論は **LoopFormBuilder + LoopSnapshotMergeBox** に完全委譲したまま、 + - FuncScanner / Stage‑B は「テキストスキャン+JSON 組み立て」の箱として綺麗に分離された状態にする。 + +## すでに前提としている状態 + +- LoopForm v2 / PHI / snapshot: + - ループ構造・PHI・break/continue スナップショットの意味論は、 + - `src/mir/loop_builder.rs` + - `src/mir/phi_core/loopform_builder.rs` + - `src/mir/phi_core/loop_snapshot_merge.rs` + に集約されており、AST ルート / JSON ルートともに同じ実装を使っている。 + - canonical `continue_merge` ブロック(ループごとに 1 つ)が導入済みで、backedge は + - `latch` → `header` + - `continue_merge` → `header` + の 2 本に限定されている。 + +- JSON v0 front: + - `src/runner/json_v0_bridge/lowering/loop_.rs` は LoopForm v2 の **薄いアダプタ**になっている。 + - ブロック ID の確保 + - `vars` / snapshot の受け渡し + - `LoopFormOps` 実装 (`LoopFormJsonOps`) + だけを担い、PHI 生成は LoopForm v2 に一元化された。 + +- 25.1e / 25.1q / 25.2: + - 変数スコープ(Carrier / Pinned / Invariant / BodyLocalInOut)と Env_in/Env_out モデルは文書と実装が一致している。 + - JSON ループ用のスモーク(`tests/json_program_loop.rs`)はすでに緑で、ループ+break/continue+body‑local exit に対して MIR 検証が通っている。 + +この Phase 25.3 では、「LoopForm / JSON front は触らずに、FuncScanner / Stage‑B ラインをその上に綺麗に載せる」ことが目的になる。 + +## タスク一覧 + +### 1. FuncScanner fib 最小ハーネス(SSA バグの確認完了) + +- 対象: + - `lang/src/compiler/tests/funcscanner_fib_min.hako` + - `FuncScannerBox.scan_all_boxes/1`(Rust lowering 時の MIR) + +- 現状: + - `NYASH_VM_VERIFY_MIR=1` 付きで `funcscanner_fib_min.hako` を実行しても、 + Undefined value / `ssa-undef-debug` は発生していない(LoopForm v2 / LoopSnapshotMergeBox 修正で解消済み)。 + - `cargo test mir_funcscanner_fib_min_ssa_debug` も緑で、FuncScanner 単体の SSA 破綻は再現しない。 + - このフェーズで導入した `__mir__` ロガーは、今後の再現時に経路観測用フックとして利用できる状態になっている。 + +- 位置づけ: + - 「FuncScanner + LoopForm v2 での Undefined Value バグの根治」は完了とみなし、 + 以降は Stage‑B 側の defs 欠落(`defs=[]`)を主ターゲットとする。 + +- 追加の SSA ガード(me-call 周り): + - Rust 側では `MirBuilder::handle_me_method_call` を `MeCallPolicyBox` によって箱化し、 + - `Box.method/Arity` lowered 関数が存在する場合は、従来どおり `me` を先頭引数とする Global call(インスタンス文脈の me-call)。 + - 存在しない場合は、`handle_static_method_call(cls, method, arguments)` にフォールバックし、 + static helper(`FuncScannerBox._parse_params` / `_strip_comments` など)への呼び出しとして扱うようにした。 + - これにより、static box 文脈で「実体のない me を receiver とする Method call」が生成される経路が閉じられ、 + FuncScannerBox._scan_methods/4 + `_parse_params` + `_strip_comments` のラインは SSA 的に安全になっている。 + +### 2.5. MIR ロガー (__mir__) による観測(Dev 専用) + +- 位置づけ: + - Phase 25.3 では、FuncScanner / Stage‑B のような `.hako` 側ロジックを LoopForm v2 上でデバッグしやすくするため、 + 専用の MIR ログ構文 `__mir__` を導入する(実行意味論には影響しない dev 専用 Hook)。 +- 構文: + - `__mir__.log("label", v1, v2, ...)` + - lowering 時に `MirInstruction::DebugLog { message: "label", values: [v1, v2, ...] }` へ変換される。 + - `__mir__.mark("label")` + - 値無し版の `DebugLog` として `debug_log "label"` だけを差し込む。 +- 振る舞い: + - VM 実行時は `NYASH_MIR_DEBUG_LOG=1` のときだけ + - `[MIR-LOG] label: %id=value ...` + の形式でログ出力され、それ以外のときは完全に無視される(Effect::Debug のみ)。 + - LoopForm / PHI / snapshot には関与せず、単に MIR に 1 命令追加するだけの観測レイヤ。 +- 実装ポイント: + - `src/mir/builder/calls/build.rs` 内の `build_method_call_impl` で、 + - receiver が `__mir__` の `__mir__.log/mark` を検出し、 + - `try_build_mir_debug_call` で `DebugLog` 命令に直接 lowering している。 + +FuncScanner / Stage‑B のデバッグ時には、`scan_all_boxes` のループ頭や `box` 検出直後に +`__mir__.log("funcscan/head", i, in_str, in_block)` などを埋め込み、VM 実行ログと合わせて +「どの経路で環境スナップショットやステートが崩れているか」を観測する想定だよ。 + +### 3. Stage‑B FuncScanner との整合性確保(主ターゲット) + +- 対象: + - `lang/src/compiler/entry/compiler_stageb.hako` + - `StageBFuncScannerBox.scan_all_boxes` + - `StageBFuncScannerBox._find_matching_brace` 他 + - `StageBDriverBox.main` 内の defs 組み立てロジック + +- やること: + - `StageBFuncScannerBox` のロジックを、可能な範囲で `FuncScannerBox` と共有・委譲する方向に寄せる。 + - `_find_matching_brace` などはすでに FuncScannerBox に委譲済みなので、 + 残りのスキャンロジックも「同じアルゴリズム / 同じ境界条件」で動くように整理する。 + - `HAKO_STAGEB_FUNCSCAN_TEST=1` で `StageBFuncScannerBox.test_fib_scan()` を走らせ、 + - `brace1/brace2` の close_idx が正しく取れること。 + - `defs_len > 0` となり、`TestBox.fib` / `Main.main` がログに出ること。 + - そのうえで `StageBDriverBox.main` が `StageBFuncScannerBox.scan_all_boxes(src)` の結果を + - Program(JSON v0).defs に正しく注入できているかを確認する。 + - ここでの主なバグは「SSA ではなく、Stage‑B が fib ソースから defs を組み立てきれていないこと」なので、 + ループ構造や LoopForm には手を入れず、Stage‑B 側のテキスト処理と defs 生成パスを中心に見る。 + +### 4. fib defs canary & ドキュメント更新 + +- 対象: + - `tools/smokes/v2/profiles/quick/core/phase251/stageb_fib_program_defs_canary_vm.sh` + - `docs/development/roadmap/phases/phase-25.1q/README.md` + - `CURRENT_TASK.md` + +- やること: + - canary スクリプトで: + - `Program.kind == "Program"` + - `defs` に `TestBox.fib` が存在すること。 + - `TestBox.fib.body` に `Loop` ノードが含まれること。 + を満たした状態で `rc=0` になるまで確認する。 + - Phase 25.1q / 25.2 の README には、 + - 「loop/PHI/スナップショットの SSOT は LoopForm v2 + LoopSnapshotMergeBox」 + - 「Phase 25.3 で FuncScanner / Stage‑B defs もこの土台の上に載せた」 + というつながりを 1〜2 行で追記する。 + - `CURRENT_TASK.md` には: + - Phase 25.3 のタスク完了状況(FuncScanner fib / Stage‑B fib canary 緑)と、 + - その後に繋がる Stage‑1 UsingResolver / Stage‑1 CLI self‑host ラインへのブリッジ + を短く整理しておく。 + +--- + +この Phase 25.3 は、 + +- LoopForm v2 / JSON front を「箱として完成済み」とみなし、 +- その上で FuncScanner / Stage‑B defs ラインを **構造的に**安定させる + +ための仕上げフェーズだよ。 +ループや PHI の意味論は触らず、テキストスキャンとスコープ設計を整えることで、self‑hosting ライン全体の足場を固めるのが狙い。+ diff --git a/lang/src/compiler/entry/func_scanner.hako b/lang/src/compiler/entry/func_scanner.hako index 2732ccea..791be216 100644 --- a/lang/src/compiler/entry/func_scanner.hako +++ b/lang/src/compiler/entry/func_scanner.hako @@ -12,90 +12,91 @@ static box FuncScannerBox { return me._scan_methods(source, box_name, 1, 0) } - // Scan all static box definitions and collect their methods. - method scan_all_boxes(source) { - // source が null の場合はスキャン対象がないので空配列を返すよ。 - if source == null { return new ArrayBox() } - local defs = new ArrayBox() - local s = "" + source + // Scan all static box definitions and collect their methods. + method scan_all_boxes(source) { + // source が null の場合はスキャン対象がないので空配列を返すよ。 + if source == null { return new ArrayBox() } + local defs = new ArrayBox() + local s = "" + source local n = s.length() - // DEBUG: 一時的に常時トレースを有効化(Stage‑B fib 用の挙動観測) - print("[funcscan/scan_all_boxes] source_len=" + ("" + n)) local i = 0 local in_str = 0 local esc = 0 local in_line = 0 local in_block = 0 + // Region + next_i 形式に統一して、ループ本体での多重 continue による + // SSA/PHI の複雑さを下げる。 loop(i < n) { + local next_i = i + 1 local ch = s.substring(i, i + 1) + if in_line == 1 { if ch == "\n" { in_line = 0 } - i = i + 1 - continue - } - if in_block == 1 { + } else if in_block == 1 { if ch == "*" && i + 1 < n && s.substring(i + 1, i + 2) == "/" { in_block = 0 - i = i + 2 - continue + next_i = i + 2 + } + } else if in_str == 1 { + if esc == 1 { + esc = 0 + } else if ch == "\\" { + esc = 1 + } else if ch == "\"" { + in_str = 0 + } + } else { + if ch == "\"" { + in_str = 1 + } else if ch == "/" && i + 1 < n { + local ch2 = s.substring(i + 1, i + 2) + if ch2 == "/" { + in_line = 1 + next_i = i + 2 + } else if ch2 == "*" { + in_block = 1 + next_i = i + 2 + } + } else { + if i + 3 <= n && s.substring(i, i + 3) == "box" && me._kw_boundary_before(s, i) == 1 && me._kw_boundary_after(s, i + 3) == 1 { + local cursor = i + 3 + cursor = me._skip_whitespace(s, cursor) + local name_start = cursor + loop(cursor < n) { + local ch_name = s.substring(cursor, cursor + 1) + if me._is_ident_char(ch_name) == 1 { cursor = cursor + 1 } else { break } + } + local box_name = s.substring(name_start, cursor) + if box_name == "" { + next_i = cursor + } else { + cursor = me._skip_whitespace(s, cursor) + if cursor >= n || s.substring(cursor, cursor + 1) != "{" { + next_i = cursor + } else { + local close_idx = me._find_matching_brace(s, cursor) + if close_idx < 0 { + // マッチしない場合はこれ以上スキャンしても意味がないのでループ終了。 + i = n + next_i = n + } else { + local body = s.substring(cursor + 1, close_idx) + local skip_main = 0 + if box_name == "Main" { skip_main = 1 } + local include_me = 0 + if box_name != "Main" { include_me = 1 } + local defs_box = me._scan_methods(body, box_name, skip_main, include_me) + me._append_defs(defs, defs_box) + next_i = close_idx + 1 + } + } + } + } } - i = i + 1 - continue - } - if in_str == 1 { - if esc == 1 { esc = 0 i = i + 1 continue } - if ch == "\\" { esc = 1 i = i + 1 continue } - if ch == "\"" { in_str = 0 i = i + 1 continue } - i = i + 1 - continue - } - if ch == "\"" { in_str = 1 i = i + 1 continue } - if ch == "/" && i + 1 < n { - local ch2 = s.substring(i + 1, i + 2) - if ch2 == "/" { in_line = 1 i = i + 2 continue } - if ch2 == "*" { in_block = 1 i = i + 2 continue } } - // DEBUG: "box" キーワード候補をチェック(Stage‑B fib 用) - if i + 3 <= n { - local candidate = s.substring(i, i + 3) - if candidate == "box" { - print("[funcscan/box_candidate] i=" + ("" + i) + " candidate=" + candidate) - } - } - if s.substring(i, i + 3) == "box" && me._kw_boundary_before(s, i) == 1 && me._kw_boundary_after(s, i + 3) == 1 { - local cursor = i + 3 - cursor = me._skip_whitespace(s, cursor) - local name_start = cursor - loop(cursor < n) { - local ch_name = s.substring(cursor, cursor + 1) - if me._is_ident_char(ch_name) == 1 { cursor = cursor + 1 } else { break } - } - local box_name = s.substring(name_start, cursor) - if box_name == "" { - i = cursor - continue - } - cursor = me._skip_whitespace(s, cursor) - if cursor >= n || s.substring(cursor, cursor + 1) != "{" { - i = cursor - continue - } - local close_idx = me._find_matching_brace(s, cursor) - if close_idx < 0 { break } - local body = s.substring(cursor + 1, close_idx) - local skip_main = 0 - if box_name == "Main" { skip_main = 1 } - local include_me = 0 - if box_name != "Main" { include_me = 1 } - local defs_box = me._scan_methods(body, box_name, skip_main, include_me) - me._append_defs(defs, defs_box) - i = close_idx + 1 - continue - } - - i = i + 1 + i = next_i } return defs } @@ -164,7 +165,8 @@ static box FuncScannerBox { // Extract params (minimal: comma-separated names) local params_str = s.substring(lparen + 1, rparen) - local params = me._parse_params(params_str) + // static helper として扱う(me ではなく FuncScannerBox に直接委譲)。 + local params = FuncScannerBox._parse_params(params_str) if include_me == 1 { local first = "" if params.length() > 0 { first = "" + params.get(0) } @@ -235,8 +237,9 @@ static box FuncScannerBox { // Extract method body (inside braces) local method_body = s.substring(lbrace + 1, rbrace) - method_body = me._strip_comments(method_body) - method_body = me._trim(method_body) + // コメント除去と trim も static helper として扱う。 + method_body = FuncScannerBox._strip_comments(method_body) + method_body = FuncScannerBox._trim(method_body) // Parse method body to JSON (statement list) local body_json = null @@ -304,14 +307,18 @@ static box FuncScannerBox { method _find_matching_brace(s, open_idx) { local n = s.length() - local i = open_idx + if open_idx < 0 || open_idx >= n { return -1 } + // open_idx 以降だけを対象に走査することで、前方の構造に依存しないようにする。 + local subs = s.substring(open_idx, n) + local m = subs.length() + local i = 0 local depth = 0 local in_str = 0 local esc = 0 local in_line = 0 local in_block = 0 - loop(i < n) { - local ch = s.substring(i, i + 1) + loop(i < m) { + local ch = subs.substring(i, i + 1) if in_line == 1 { if ch == "\n" { in_line = 0 } i = i + 1 @@ -334,8 +341,8 @@ static box FuncScannerBox { continue } if ch == "\"" { in_str = 1 i = i + 1 continue } - if ch == "/" && i + 1 < n { - local ch2 = s.substring(i + 1, i + 2) + if ch == "/" && i + 1 < m { + local ch2 = subs.substring(i + 1, i + 2) if ch2 == "/" { in_line = 1 i = i + 2 continue } if ch2 == "*" { in_block = 1 i = i + 2 continue } } @@ -347,7 +354,7 @@ static box FuncScannerBox { if ch == "}" { depth = depth - 1 i = i + 1 - if depth == 0 { return i - 1 } + if depth == 0 { return open_idx + i - 1 } continue } i = i + 1 diff --git a/lang/src/compiler/tests/funcscanner_scan_methods_min.hako b/lang/src/compiler/tests/funcscanner_scan_methods_min.hako new file mode 100644 index 00000000..45dbdc2a --- /dev/null +++ b/lang/src/compiler/tests/funcscanner_scan_methods_min.hako @@ -0,0 +1,43 @@ +// funcscanner_scan_methods_min.hako — FuncScannerBox._scan_methods minimal harness +// +// 目的: +// - static box TestBox/Main を含むソースから TestBox.body 部分だけを切り出し、 +// FuncScannerBox._scan_methods(source, "TestBox", 0, 1) を直接呼び出す。 +// - params 部分のパースと _parse_params/_trim の呼び出しが SSA 的に安全であることを確認する。 +// +// 振る舞い: +// - fib と同等の TestBox/Main を src に埋め込む。 +// - FuncScannerBox._find_matching_brace で TestBox の '{'〜'}' 範囲を取得。 +// - 本文だけを body1 として抜き出し、FuncScannerBox._scan_methods に渡す。 +// - defs.len と各定義の box/name をログに出力する(Stage‑B 側と比較しやすくする)。 + +using lang.compiler.entry.func_scanner as FuncScannerBox + +static box Main { + method main(args) { + local src = "static box TestBox {\n method fib(n) {\n local i = 0\n local a = 0\n local b = 1\n loop(i < n) {\n local t = a + b\n a = b\n b = t\n i = i + 1\n }\n return b\n }\n}\n\nstatic box Main {\n method main(args) {\n local t = new TestBox()\n return t.fib(6)\n }\n}\n" + + // TestBox 本体の brace 範囲を FuncScannerBox の実装で検出 + local open1 = 19 + local close1 = FuncScannerBox._find_matching_brace(src, open1) + print("[scan_methods] brace1 open=" + ("" + open1) + " close=" + ("" + close1)) + if close1 < 0 { return 1 } + + // "box TestBox {" の直後から close1 直前までを body1 として切り出す + local body1 = src.substring(open1 + 1, close1) + print("[scan_methods] body1_len=" + ("" + body1.length())) + + // FuncScannerBox._scan_methods を直接呼び出してメソッド定義を抽出 + local defs1 = FuncScannerBox._scan_methods(body1, "TestBox", 0, 1) + local n1 = defs1.length() + print("[scan_methods] defs1.len=" + ("" + n1)) + local i = 0 + loop(i < n1) { + local d = defs1.get(i) + print("[scan_methods] def1[" + ("" + i) + "] box=" + ("" + d.get("box")) + " name=" + ("" + d.get("name"))) + i = i + 1 + } + + return 0 + } +} diff --git a/src/mir/builder.rs b/src/mir/builder.rs index bef7d3ad..970602cf 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -533,7 +533,7 @@ impl MirBuilder { }; instruction = MirInstruction::Call { dst: *dst, - func: crate::mir::ValueId::new(0), // Legacy compatibility + func: crate::mir::ValueId::INVALID, // Legacy dummy (not a real SSA use) callee: Some(new_callee), args: args.clone(), effects: *effects, diff --git a/src/mir/builder/calls/build.rs b/src/mir/builder/calls/build.rs index d0210f6f..f345c9a7 100644 --- a/src/mir/builder/calls/build.rs +++ b/src/mir/builder/calls/build.rs @@ -99,6 +99,15 @@ impl MirBuilder { eprintln!("[builder] method-call object kind={} method={}", kind, method); } + // 0. Dev-only: __mir__.log / __mir__.mark → MirInstruction::DebugLog へ直接 lowering + if let ASTNode::Variable { name: obj_name, .. } = &object { + if obj_name == "__mir__" { + if let Some(result) = self.try_build_mir_debug_call(&method, &arguments)? { + return Ok(result); + } + } + } + // 1. Static box method call: BoxName.method(args) if let ASTNode::Variable { name: obj_name, .. } = &object { if let Some(result) = self.try_build_static_method_call(obj_name, &method, &arguments)? { @@ -407,6 +416,58 @@ impl MirBuilder { Ok(None) } + /// Dev-only: __mir__.log / __mir__.mark → MirInstruction::DebugLog への変換 + /// + /// 構文: + /// __mir__.log("label", v1, v2, ...) + /// __mir__.mark("label") + /// + /// - 第一引数は String リテラル想定(それ以外はこのハンドラをスキップして通常の解決に回す)。 + /// - 戻り値は Void 定数の ValueId(式コンテキストでも型破綻しないようにするため)。 + fn try_build_mir_debug_call( + &mut self, + method: &str, + arguments: &[ASTNode], + ) -> Result, String> { + if method != "log" && method != "mark" { + return Ok(None); + } + + if arguments.is_empty() { + return Err("__mir__.log/__mir__.mark requires at least a label argument".to_string()); + } + + // 第一引数は String リテラルのみ対応(それ以外は通常経路にフォールバック) + let label = match &arguments[0] { + ASTNode::Literal { + value: LiteralValue::String(s), + .. + } => s.clone(), + _ => { + // ラベルがリテラルでない場合はこのハンドラをスキップし、通常の static box 解決に任せる + return Ok(None); + } + }; + + // 残りの引数を評価して ValueId を集める + let mut values: Vec = Vec::new(); + if method == "log" { + for arg in &arguments[1..] { + values.push(self.build_expression(arg.clone())?); + } + } + + // MIR に DebugLog 命令を 1 つ挿入(意味論は NYASH_MIR_DEBUG_LOG=1 のときにだけ効く) + self.emit_instruction(MirInstruction::DebugLog { + message: label, + values, + })?; + + // 式コンテキスト用に Void 定数を返す(呼び出し元では通常使われない) + let void_id = crate::mir::builder::emission::constant::emit_void(self); + Ok(Some(void_id)) + } + /// Try static method fallback (name+arity) fn try_static_method_fallback( &mut self, diff --git a/src/mir/builder/calls/unified_emitter.rs b/src/mir/builder/calls/unified_emitter.rs index dd216961..eaee2e5f 100644 --- a/src/mir/builder/calls/unified_emitter.rs +++ b/src/mir/builder/calls/unified_emitter.rs @@ -201,7 +201,7 @@ impl UnifiedCallEmitterBox { // For Phase 2: Convert to legacy Call instruction with new callee field (use finalized operands) let legacy_call = MirInstruction::Call { dst: mir_call.dst, - func: ValueId::new(0), // Dummy value for legacy compatibility + func: ValueId::INVALID, // Dummy value for legacy compatibility (not a real SSA use) callee: Some(callee), args: args_local, effects: mir_call.effects, diff --git a/src/mir/builder/lifecycle.rs b/src/mir/builder/lifecycle.rs index 8d258df7..fe3ca58c 100644 --- a/src/mir/builder/lifecycle.rs +++ b/src/mir/builder/lifecycle.rs @@ -325,6 +325,12 @@ impl super::MirBuilder { // Dev stub: provide condition_fn when missing to satisfy predicate calls in JSON lexers // Returns integer 1 (truthy) and accepts one argument (unused). + // + // NOTE: + // - MirFunction::new() はシグネチャの params に応じて + // [ValueId(0)..ValueId(param_count-1)] を事前に予約する。 + // - ここでは追加の next_value_id()/params.push() は行わず、 + // 予約済みのパラメータ集合をそのまま使う。 if module.functions.get("condition_fn").is_none() { let sig = FunctionSignature { name: "condition_fn".to_string(), @@ -334,9 +340,6 @@ impl super::MirBuilder { }; let entry = BasicBlockId::new(0); let mut f = MirFunction::new(sig, entry); - // parameter slot (unused in body) - let _param = f.next_value_id(); - f.params.push(_param); // body: const 1; return it(FunctionEmissionBox を使用) let one = crate::mir::function_emission::emit_const_integer(&mut f, entry, 1); crate::mir::function_emission::emit_return_value(&mut f, entry, one); diff --git a/src/mir/builder/method_call_handlers.rs b/src/mir/builder/method_call_handlers.rs index 94981e58..97082604 100644 --- a/src/mir/builder/method_call_handlers.rs +++ b/src/mir/builder/method_call_handlers.rs @@ -9,6 +9,69 @@ use crate::mir::builder::builder_calls::CallTarget; use crate::mir::{MirInstruction, TypeOpKind}; use crate::mir::builder::calls::function_lowering; +/// Me-call 専用のポリシー箱。 +/// +/// - 責務: +/// - me.method(...) を「インスタンス呼び出し」か「static メソッド呼び出し」か判定する。 +/// - static box 文脈で実体のない receiver を生まないように、静的メソッド降下にフォールバックする。 +struct MeCallPolicyBox; + +impl MeCallPolicyBox { + fn resolve_me_call( + builder: &mut MirBuilder, + method: &str, + arguments: &[ASTNode], + ) -> Result, String> { + // Instance box: prefer enclosing box method (lowered function) if存在 + let enclosing_cls: Option = builder + .current_function + .as_ref() + .and_then(|f| f.signature.name.split('.').next().map(|s| s.to_string())); + + if let Some(cls) = enclosing_cls.as_ref() { + let mut arg_values = Vec::with_capacity(arguments.len()); + for a in arguments { + arg_values.push(builder.build_expression(a.clone())?); + } + let arity = arg_values.len(); + let fname = + function_lowering::generate_method_function_name(cls, method, arity); + let exists = if let Some(ref module) = builder.current_module { + module.functions.contains_key(&fname) + } else { + false + }; + if exists { + // Pass 'me' as first arg + let me_id = builder.build_me_expression()?; + let mut call_args = Vec::with_capacity(arity + 1); + call_args.push(me_id); + call_args.extend(arg_values.into_iter()); + let dst = builder.next_value_id(); + // Emit as unified global call to lowered function + builder.emit_unified_call( + Some(dst), + CallTarget::Global(fname.clone()), + call_args, + )?; + builder.annotate_call_result_from_func_name(dst, &fname); + return Ok(Some(dst)); + } + + // Fallback: treat me.method(...) as a static method on the enclosing box. + // - 旧挙動では me を Box 値として Method 呼び出ししようとしていたが、 + // static box 文脈では実体インスタンスが存在しないため UndefinedValue を生みやすい。 + // - ここでは receiver を持たない Global call に揃え、FuncScannerBox などの + // static ヘルパー呼び出しを安全に扱う。 + let static_dst = + builder.handle_static_method_call(cls, method, arguments)?; + return Ok(Some(static_dst)); + } + + Ok(None) + } +} + impl MirBuilder { /// Handle static method calls: BoxName.method(args) pub(super) fn handle_static_method_call( @@ -77,43 +140,7 @@ impl MirBuilder { method: &str, arguments: &[ASTNode], ) -> Result, String> { - // Instance box: prefer enclosing box method (lowered function) if存在 - let enclosing_cls: Option = self - .current_function - .as_ref() - .and_then(|f| f.signature.name.split('.').next().map(|s| s.to_string())); - - if let Some(cls) = enclosing_cls.as_ref() { - let mut arg_values = Vec::with_capacity(arguments.len()); - for a in arguments { - arg_values.push(self.build_expression(a.clone())?); - } - let arity = arg_values.len(); - let fname = - function_lowering::generate_method_function_name(cls, method, arity); - let exists = if let Some(ref module) = self.current_module { - module.functions.contains_key(&fname) - } else { - false - }; - if exists { - // Pass 'me' as first arg - let me_id = self.build_me_expression()?; - let mut call_args = Vec::with_capacity(arity + 1); - call_args.push(me_id); - call_args.extend(arg_values.into_iter()); - let dst = self.next_value_id(); - // Emit as unified global call to lowered function - self.emit_unified_call( - Some(dst), - CallTarget::Global(fname.clone()), - call_args, - )?; - self.annotate_call_result_from_func_name(dst, &fname); - return Ok(Some(dst)); - } - } - Ok(None) + MeCallPolicyBox::resolve_me_call(self, method, arguments) } /// Handle standard Box/Plugin method calls (fallback) diff --git a/src/mir/instruction/methods.rs b/src/mir/instruction/methods.rs index fb1ecff1..8efa72b7 100644 --- a/src/mir/instruction/methods.rs +++ b/src/mir/instruction/methods.rs @@ -185,8 +185,15 @@ impl MirInstruction { MirInstruction::Return { value } => value.map(|v| vec![v]).unwrap_or_default(), - MirInstruction::Call { func, args, .. } => { - let mut used = vec![*func]; + MirInstruction::Call { func, callee, args, .. } => { + // func は legacy 経路では「関数値」を指すが、現在の unified 経路では + // callee にメタ情報が入り、func はダミー (0) になることがある。 + // callee が None のときだけ func を SSA 値として扱い、それ以外 + // (callee=Some(..))では args のみを使用値とみなす。 + let mut used: Vec = Vec::new(); + if callee.is_none() { + used.push(*func); + } used.extend(args); used } diff --git a/src/mir/instruction_kinds/mod.rs b/src/mir/instruction_kinds/mod.rs index 62fc2261..46333e84 100644 --- a/src/mir/instruction_kinds/mod.rs +++ b/src/mir/instruction_kinds/mod.rs @@ -581,11 +581,21 @@ impl CallLikeInst { pub fn used(&self) -> Vec { match self { CallLikeInst::Call { func, args, .. } => { - let mut v = vec![*func]; v.extend(args.iter().copied()); v + let mut v = Vec::new(); + // func は legacy 経路では「関数値」を指すが、unified 経路では + // ValueId::INVALID がダミーとして入る。INVALID の場合は SSA の + // 使用値としては数えず、args のみを使用値とみなす。 + if *func != ValueId::INVALID { + v.push(*func); + } + v.extend(args.iter().copied()); + v } CallLikeInst::BoxCall { box_val, args, .. } | CallLikeInst::PluginInvoke { box_val, args, .. } => { - let mut v = vec![*box_val]; v.extend(args.iter().copied()); v + let mut v = vec![*box_val]; + v.extend(args.iter().copied()); + v } CallLikeInst::ExternCall { args, .. } => args.clone(), } diff --git a/src/mir/verification.rs b/src/mir/verification.rs index f40bda3b..7d27cee5 100644 --- a/src/mir/verification.rs +++ b/src/mir/verification.rs @@ -96,6 +96,13 @@ impl MirVerifier { block, instruction_index ); + if let Some(bb) = function.blocks.get(block) { + let inst_opt = + bb.all_instructions().nth(*instruction_index); + if let Some(inst) = inst_opt { + eprintln!("[mir-ssa-debug-inst] inst={:?}", inst); + } + } } VerificationError::DominatorViolation { value, diff --git a/src/mir/verification/ssa.rs b/src/mir/verification/ssa.rs index 8f199a29..9f95f0b6 100644 --- a/src/mir/verification/ssa.rs +++ b/src/mir/verification/ssa.rs @@ -31,6 +31,10 @@ pub fn check_ssa_form(function: &MirFunction) -> Result<(), Vec