diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index b1300ea6..a8124290 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -31,6 +31,60 @@ Update (2025-11-18 — Phase 25.1k: LoopSSA v2 (.hako) & Stage‑B harness 追 - K‑C: PhiInjectorBox に Carrier/Pinned ベースの v2 入口を追加(既存 `_collect_phi_vars` は当面維持)。 - K‑D: Stage‑B Test3 の `%0` SSA 問題が悪化していないことを確認しつつ、LoopSSA 有効/無効の切り分けを残す。 +Update (2025-11-18 — Phase 25.1b: Stage‑B/Ny selfhost parser depth guards hotfix) +- Context: + - Stage‑B / Ny selfhost ラインでは、`ParserBox.parse_program2` → `ParserStmtBox.parse` → `ParserControlBox.*` の再帰ガードに `HAKO_STAGEB_*_DEPTH` 環境変数を使っており、誤ループや自己再帰を検出する設計になっている。 + - ただしこれまでの実装では、正常終了パスで depth を 0 に戻していない箇所があり、一度ガードが立つとその後のすべての呼び出しが「再帰検出扱い」になってしまい、空文列 `""` やダミー Break/Continue を返すことで **進捗ガード頼みの疑似無限ループ** を引き起こしていた。 + - 特に `.ny` の大きなソースを inline selfhost 経路(`ny_selfhost_inline.sh` / `inline_selfhost_emit.hako` 相当)で処理した場合に、`ParserBox.parse_program2` が 60s 付近まで進んでから `Undefined variable: local` を出す背景の一つになっていた疑いがある。 +- Done: + - `lang/src/compiler/parser/stmt/parser_stmt_box.hako`: + - `ParserStmtBox.parse` の再帰ガード: + - 入口で `HAKO_STAGEB_STMT_DEPTH` を `"1"` にセットし、すでに `"!=0"` のときは `[stageb/recursion] ParserStmtBox.parse recursion detected` を出して空文 `""` を返す構造は維持。 + - そのうえで「正常に 1 文パースして return する」すべての分岐で `env.set("HAKO_STAGEB_STMT_DEPTH", "0")` を明示的に呼ぶように修正し、**1 文=1 ガードサイクル** として閉じるようにした。 + - 対象: `@extern_c` アノテーション、`using` 文(`parse_using`)、代入文(`Local` 生成)、`return` 文、`local` 宣言(Stage‑3)、`if/loop/break/continue/throw/try` を各 Box に委譲する分岐、および既存の Expr fallback。 + - これにより、1 回目の parse 成功後に `HAKO_STAGEB_STMT_DEPTH` が 1 のまま残り、2 回目以降がすべて再帰検出ブランチに落ちるバグを解消(Stage‑B/Stage‑1/自前 Ny selfhost での multi‑stmt ソースを安定して扱えるようにした)。 + - `lang/src/compiler/parser/stmt/parser_control_box.hako`: + - `parse_break` / `parse_continue` の Stage‑3 パスで、正常終了時にもそれぞれ `HAKO_STAGEB_BREAK_DEPTH` / `HAKO_STAGEB_CONTINUE_DEPTH` を `"0"` に戻すように修正。 + - これにより、break/continue を含むループを Stage‑3 構文でパースした後も、次の `parse_stmt2` 呼び出しからは再び通常パスに戻れるようになった(break/continue 自体は既存どおり `{type:"Break"}` / `{type:"Continue"}` を返す)。 +- Quick verification: + - `tools/ny_selfhost_inline.sh local_tests/test_large_method_break.nyash target/release/hakorune`: + - 実行時に Rust VM の `Invalid value: use of undefined value ValueId(..)` や無限ループは発生せず、inline selfhost パーサ経路は正常終了することを確認。 + - これにより Ny selfhost パイプラインの **「Rust VM の SSA/PHI 破綻で即死」フェーズは抜けており、残る課題は .hako 側 Stage‑B コンパイラ(構文/using/Stage‑3 周辺)の rc=1 に集約されている** ことがはっきりした。 +- Current status of Phase 25.1 selfhost canaries: + - `tools/smokes/v2/profiles/quick/core/phase251/stageb_fib_program_defs_canary_vm.sh`: + - 依然として Stage‑B rc=1 で FAIL(Program(JSON v0) の抽出以前で落ちている)。ログ上は GC/SSA ではなく Stage‑B 本体側のエラーとして見えている。 + - `tools/smokes/v2/profiles/quick/core/phase251/selfhost_cli_run_basic_vm.sh`: + - `HAKO_SELFHOST_BUILDER_FIRST=1` + `HAKO_MIR_BUILDER_CLI_RUN=1` で selfhost builder を優先した場合に、Stage‑B / direct MIR emit 双方 rc=1 のまま(`HakoCli.run` の MIR(JSON) が生成されない)。 + - 代表的な Rust MIR/SSA スモーク(`mir_stage1_using_resolver_*` / `mir_loopform_exit_phi` / `mir_stageb_loop_break_continue`)および Ny selfhost inline テストでは、Rust VM の `Undefined value` / `%0` 二重定義は再現しておらず、25.1 の Rust 側 SSA/PHI 修正は安定したとみなす。 + + → 今後の 25.1b/25.1c では、Ny selfhost/Numeric core の前に **Stage‑B (`compiler_stageb.hako`) と Stage‑1 CLI (`HakoCli.run`) の .hako 側構文/using/Stage‑3 ロジックを構造的に整理し、rc=1 の原因を 1 関数=1 テスト単位で切り出して潰していく** フェーズに入る。 + +Update (2025-11-18 — Phase 25.1c: Stage‑B / Stage‑1 CLI 構造デバッグプラン) +- Context: + - Phase 25.1c は `env.*` / `hostbridge.*` / `env.box_introspect.*` の責務整理に加えて、Stage‑B Main (`compiler_stageb.hako`) と Stage‑1 CLI (`HakoCli.run`) の構造を「Region/スコープが追いやすい形」に整えるフェーズとして位置づけた。 + - Rust 側ではすでに `Region / RefSlotKind / FunctionSlotRegistry + ControlForm` により Loop/If のスコープが安定しており、`NYASH_REGION_TRACE=1` で Stage‑B 由来関数の Region/slot を観測できる状態。これを .hako 側 Stage‑B 実装の「正解ビュー」として使う。 + - 一方、25.1b の depth guard hotfix により Ny selfhost パーサ/Stage‑B の `local` 系バグのうち「疑似再帰で進捗ガード頼みになる」ものは排除され、残る問題は主に Stage‑B 本体(compiler_stageb.hako)の rc=1 と CLI 経路の構造に集約されている。 +- Plan (25.1c, initial steps): + 1) Stage‑B rc=1 の発生箇所を箱単位へ縮小: + - 代表 canary を 2 本ターゲットにする: + - `tools/smokes/v2/profiles/quick/core/phase251/stageb_fib_program_defs_canary_vm.sh`(fib 風 defs + Loop の presence を確認する canary)。 + - `tools/smokes/v2/profiles/quick/core/phase251/selfhost_cli_run_basic_vm.sh`(HakoCli.run の selfhost MIR 生成 canary)。 + - `compiler_stageb.hako` の Stage‑B Main を「箱理論」に沿って分割した設計(`StageBArgsBox` / `StageBBodyExtractorBox` / `StageBDriverBox`)に合わせて、各箱入口/出口に `[stageb/trace:.:enter|leave]` を付与。 + → どの箱のどのメソッドで rc=1 になっているかをまずログだけで特定する(挙動は変えない)。 + 2) Rust Region ログと .hako 側のスコープ観測を揃える: + - Rust 側の `src/mir/region/*` + `NYASH_REGION_TRACE=1` をオンにし、`StageBBodyExtractorBox.*` 周辺ループ/if の Region 情報(entry/exit/slots)をログとして取得する。 + - .hako 側 Stage‑B 実装に観測専用 Box(案: `StageBRegionObserverBox`)を追加し、`enter_region(kind, name, slots_json)` / `leave_region()` などの API で「箱名+制御構造+生きているローカル名」のみ JSON で出力する。 + - Stage‑B 本体ではこの Box を外側ループ・内側 if などの境界で呼び出し、Rust の `[region/observe]` ログと .hako ログを突き合わせて、「どのスコープでどのローカル(例: `body_src` / `bundle_srcs` / `require_mods`)が欠損しているか」を構造レベルで炙り出す。 + 3) Stage‑B 用ミニマムハーネスを Rust / .hako 両方に用意: + - 100〜200 行規模の最小 Hako サンプル(`using` + 1 box + 1 loop 程度)を `lang/src/compiler/tests/stageb_min_sample.hako` 相当として固定し、Rust 側では AST→MIR→`NYASH_VM_VERIFY_MIR=1` を通すテストを維持。 + - .hako 側では同じサンプルを入力とする mini driver(`StageBDriverMiniBox` のようなもの)を用意し、`StageBArgsBox.resolve_src` → `StageBBodyExtractorBox.build_body_src` → `ParserBox.parse_program2` だけを見る canary を追加。 + → fib / HakoCli.run より軽いケースで Stage‑B の構造バグを素早く再現・修正できるようにする。 + 4) Stage‑1 CLI selfhost ラインは Stage‑B 修正後に再アタック: + - 上記 1)〜3) で Stage‑B canary(fib defs / stageb_min / mini driver)の rc=1 を解消したあとに、25.1b 側の selfhost builder / `HakoCli.run` MIR 生成 canary を再度実行し、Program(JSON v0)→MIR(JSON) の差分を Rust ラインと比較していく。 +- Goal for 25.1c: + - Stage‑B (`compiler_stageb.hako`) と Stage‑1 CLI (`HakoCli.run`) に関する「rc=1 / Stage‑B fail」の原因を、Region/スコープ構造まで落とし込んだうえで 1 箱単位で潰せる状態にすること。 + - Rust Region レイヤを正としつつ、.hako 側でも同等の Region 観測レイヤ(RegionBox 的な構造)を持つことで、今後の GC/寿命設計や selfhost 側 LoopSSA v2 への追従を見通し良く行えるようにしておく。 + Update (2025-11-18 — Phase 25.1l: Region/GC 観測レイヤー導入(Rust 側のみ) — completed) - Context: - LoopForm v2 / ControlForm / Conservative PHI Box により、If/Loop の SSA/PHI は Rust 側で安定しているが、 diff --git a/docs/development/roadmap/phases/phase-25.1c/README.md b/docs/development/roadmap/phases/phase-25.1c/README.md index a00ff313..97a4b731 100644 --- a/docs/development/roadmap/phases/phase-25.1c/README.md +++ b/docs/development/roadmap/phases/phase-25.1c/README.md @@ -85,3 +85,38 @@ Status: planning(構造整理フェーズ・挙動は変えない) - Phase 25.1b: selfhost builder / multi‑carrier / BoxTypeInspector 実装フェーズ(機能側)。 - Phase 25.1c: そのうち「env.* / hostbridge.* / BoxIntrospect」に加えて、Stage‑B Main / LoopBuilder / builder 観測レイヤの構造と責務も整理するメタフェーズ(構造側)。 - 挙動を変えないこと(Fail‑Fast / default path は現状維持)を前提に、小さな差分で進める。 + +### デバッグ方針(25.1c で踏みたい順番) + +- 1) Stage‑B rc=1 の発生箇所を箱単位まで特定する + - 代表 canary: + - `tools/smokes/v2/profiles/quick/core/phase251/stageb_fib_program_defs_canary_vm.sh` + - `tools/smokes/v2/profiles/quick/core/phase251/selfhost_cli_run_basic_vm.sh` + - まずは `compiler_stageb.hako` の流れを箱ごとに分解してログする: + - `StageBArgsBox.resolve_src` + - `StageBBodyExtractorBox.build_body_src` + - `ParserBox.parse_program2` + - `FuncScannerBox.scan_all_boxes` + - 各箱の入口/出口に `[stageb/trace:.:enter|leave]` のような軽いタグを置き、どの箱が rc=1 の直前で止まっているかを特定する(挙動は変えない)。 + +- 2) Rust Region レイヤを「正解ビュー」として .hako 側を寄せる + - Rust 側にはすでに `Region / RefSlotKind / FunctionSlotRegistry + ControlForm` があり、`StageBBodyExtractorBox.*` 周辺のスロット(`src/body_src/bundle_*` など)がどの Loop/If Region に属しているかを `NYASH_REGION_TRACE=1` で観測できる。 + - 25.1c では .hako 側に観測専用 Box(案: `StageBRegionObserverBox`)を追加し、 + - `enter_region(kind, name, slots_json)` + - `leave_region()` + のような API で「箱名・構造名・スロット名の集合」だけを JSON で print する。 + - `StageBBodyExtractorBox` の外側ループ・内側 if など、問題になりやすい箇所でこの Box を呼び出し、Rust の `[region/observe]` ログと「木構造+スロット名」が対応しているかを確認する。 + → ずれている Region(例: `body_src` などが早期に欠落するスコープ)から優先的に修正する。 + +- 3) Stage‑B 用の極小ハーネスを Rust / .hako 両方に用意する + - fib canary はやや重いため、100〜200 行程度の「using + 1 box + 1 loop」だけの最小サンプルを `lang/src/compiler/tests/stageb_min_sample.hako` のようなファイルとして固定する。 + - Rust 側: + - そのサンプルを AST→MIR に通し、`NYASH_VM_VERIFY_MIR=1` で `Undefined value` が出ないことを確認する小さなテストを用意(既存の Stage‑B 向け MIR テスト群に揃える)。 + - .hako 側: + - 同じサンプルを入力として `StageBDriverBox` の簡易版(mini driver)を作り、`StageBArgsBox.resolve_src` → `StageBBodyExtractorBox.build_body_src` → `ParserBox.parse_program2` だけを通す driver を追加する。 + - これにより、Stage‑B/LoopBuilder に対する修正を「本番 compiler_stageb.hako 全体」ではなく「ミニマムな Hako 断片」で検証できるようにする。 + +- 4) Stage‑1 CLI (`HakoCli.run`) の selfhost ラインは Stage‑B が緑になってから扱う + - `selfhost_cli_run_basic_vm.sh` の現状の失敗は、Stage‑B が rc=1 で Program(JSON) を 1 行も返していないことが原因であり、HakoCli.run の MIR 生成まで到達していない。 + - 25.1c ではまず Stage‑B 側(fib defs / stageb_min / mini driver)を rc=0 に戻し、 + その Program(JSON v0) を固定入力にして Phase 25.1b 側の selfhost builder / HakoCli.run MIR を Rust ラインと diff する、という順番で進める。 diff --git a/lang/src/compiler/entry/compiler_stageb.hako b/lang/src/compiler/entry/compiler_stageb.hako index ee53b1b1..c6bc3ebe 100644 --- a/lang/src/compiler/entry/compiler_stageb.hako +++ b/lang/src/compiler/entry/compiler_stageb.hako @@ -17,9 +17,28 @@ using lang.compiler.entry.func_scanner as FuncScannerBox using lang.compiler.entry.using_resolver as Stage1UsingResolverBox using lang.compiler.builder.mod as CompilerBuilder +// Dev-only trace helper (Phase 25.1c) +// - Enabled when HAKO_STAGEB_TRACE=1 +// - Keeps Stage-B behavior unchanged(ログのみ追加) +static box StageBTraceBox { + log(label) { + local flag = env.get("HAKO_STAGEB_TRACE") + if flag == null { return 0 } + if ("" + flag) != "1" { return 0 } + // label が null/Void でも落ちないように守る(dev専用) + local msg = "[stageb/trace]" + if label != null { + msg = msg + " " + ("" + label) + } + print(msg) + return 0 + } +} + // Phase 25.1c: CLI argument → source resolution static box StageBArgsBox { resolve_src(args) { + StageBTraceBox.log("StageBArgsBox.resolve_src:enter") // 1) Collect source from args or env local src = null local src_file = null @@ -43,6 +62,13 @@ static box StageBArgsBox { // Original: if src == null { src = env.local.get("HAKO_SOURCE") } if src == null { src = "return 0" } + { + // Trace final source length(dev専用) + local l = 0 + if src != null { l = ("" + src).length() } + StageBTraceBox.log("StageBArgsBox.resolve_src:return_len=" + ("" + l)) + } + return src } } @@ -50,6 +76,12 @@ static box StageBArgsBox { // Phase 25.1c: Body extraction + bundle + using + trim static box StageBBodyExtractorBox { build_body_src(src, args) { + { + //入口トレース: 入力ソース長と引数有無 + local l = 0 + if src != null { l = ("" + src).length() } + StageBTraceBox.log("StageBBodyExtractorBox.build_body_src:enter len=" + ("" + l)) + } // ============================================================================ // Depth guard: prevent accidental recursion inside Stage‑B body extractor // ============================================================================ @@ -609,6 +641,13 @@ static box StageBBodyExtractorBox { if e > b { body_src = s.substring(b, e) } else { body_src = "" } } + { + //出口トレース: 抽出後 body_src 長 + local l2 = 0 + if body_src != null { l2 = ("" + body_src).length() } + StageBTraceBox.log("StageBBodyExtractorBox.build_body_src:return_len=" + ("" + l2)) + } + // Clear depth guard before returning env.set("HAKO_STAGEB_BODY_DEPTH", "0") return body_src @@ -630,7 +669,14 @@ static box StageBDriverBox { env.set("HAKO_STAGEB_DRIVER_DEPTH", "1") } + StageBTraceBox.log("StageBDriverBox.main:enter") + local src = StageBArgsBox.resolve_src(args) + { + local l = 0 + if src != null { l = ("" + src).length() } + StageBTraceBox.log("StageBDriverBox.main:after_resolve_src len=" + ("" + l)) + } // 2) Stage‑3 acceptance default ON for selfhost (env may turn off; keep tolerant here) local p = new ParserBox() @@ -643,11 +689,22 @@ static box StageBDriverBox { // local externs_json = p.get_externs_json() local body_src = StageBBodyExtractorBox.build_body_src(src, args) + { + local l2 = 0 + if body_src != null { l2 = ("" + body_src).length() } + StageBTraceBox.log("StageBDriverBox.main:after_build_body_src len=" + ("" + l2)) + } // 6) Parse and emit Stage‑1 JSON v0 (Program) // Bridge(JSON v0) が Program v0 を受け取り MIR に lowering するため、ここでは AST(JSON v0) を出力する。 // 既定で MIR 直出力は行わない(重い経路を避け、一行出力を保証)。 local ast_json = p.parse_program2(body_src) + { + // AST(JSON v0) の長さを軽く観測 + local la = 0 + if ast_json != null { la = ("" + ast_json).length() } + StageBTraceBox.log("StageBDriverBox.main:after_parse_program2 len=" + ("" + la)) + } // 6.3) Apply SSA transformations (CompilerBuilder pipeline) { @@ -686,6 +743,12 @@ static box StageBDriverBox { // Use FuncScannerBox to extract method definitions from all boxes local methods = FuncScannerBox.scan_all_boxes(src) + { + local cnt = 0 + if methods != null { cnt = methods.length() } + StageBTraceBox.log("StageBDriverBox.main:func_scan methods=" + ("" + cnt)) + } + // Build defs JSON array if methods.length() > 0 { defs_json = ",\"defs\":[" @@ -734,6 +797,7 @@ static box StageBDriverBox { } print(ast_json) + StageBTraceBox.log("StageBDriverBox.main:exit rc=0") // Clear depth guard before returning env.set("HAKO_STAGEB_DRIVER_DEPTH", "0") return 0 diff --git a/lang/src/compiler/parser/stmt/parser_control_box.hako b/lang/src/compiler/parser/stmt/parser_control_box.hako index 3e5242d6..54ce11f1 100644 --- a/lang/src/compiler/parser/stmt/parser_control_box.hako +++ b/lang/src/compiler/parser/stmt/parser_control_box.hako @@ -176,6 +176,8 @@ static box ParserControlBox { if j < src.length() { j = j + 1 } else { j = src.length() } } ctx.gpos_set(j) + // Reset recursion guard before returning (Stage-3 path) + env.set("HAKO_STAGEB_BREAK_DEPTH", "0") return "{\"type\":\"Break\"}" } @@ -205,6 +207,8 @@ static box ParserControlBox { if j < src.length() { j = j + 1 } else { j = src.length() } } ctx.gpos_set(j) + // Reset recursion guard before returning (Stage-3 path) + env.set("HAKO_STAGEB_CONTINUE_DEPTH", "0") return "{\"type\":\"Continue\"}" } diff --git a/lang/src/compiler/parser/stmt/parser_stmt_box.hako b/lang/src/compiler/parser/stmt/parser_stmt_box.hako index b8ecbb25..3a3df5f1 100644 --- a/lang/src/compiler/parser/stmt/parser_stmt_box.hako +++ b/lang/src/compiler/parser/stmt/parser_stmt_box.hako @@ -53,12 +53,17 @@ static box ParserStmtBox { ctx.gpos_set(j) // Record annotation in parser context and emit no statement ctx.add_extern_c(sym, func_name) + // Reset recursion guard before returning(1回の parse 呼び出しごとに depth をクリアする) + env.set("HAKO_STAGEB_STMT_DEPTH", "0") return "" } // using statement if ctx.starts_with_kw(src, j, "using") == 1 { - return me.parse_using(src, j, stmt_start, ctx) + local out_using = me.parse_using(src, j, stmt_start, ctx) + // Reset recursion guard before returning + env.set("HAKO_STAGEB_STMT_DEPTH", "0") + return out_using } // assignment: IDENT '=' expr @@ -90,6 +95,8 @@ static box ParserStmtBox { if k0 < src.length() { k0 = k0 + 1 } else { k0 = src.length() } } ctx.gpos_set(k0) + // Reset recursion guard before returning + env.set("HAKO_STAGEB_STMT_DEPTH", "0") return "{\"type\":\"Local\",\"name\":\"" + name0 + "\",\"expr\":" + expr_json0 + "}" } } @@ -115,6 +122,8 @@ static box ParserStmtBox { if j < src.length() { j = j + 1 } else { j = src.length() } } ctx.gpos_set(j) + // Reset recursion guard before returning + env.set("HAKO_STAGEB_STMT_DEPTH", "0") return "{\"type\":\"Return\",\"expr\":" + expr_json_ret + "}" } @@ -157,32 +166,52 @@ static box ParserStmtBox { if j < src.length() { j = j + 1 } else { j = src.length() } } ctx.gpos_set(j) + // Reset recursion guard before returning + env.set("HAKO_STAGEB_STMT_DEPTH", "0") return "{\"type\":\"Local\",\"name\":\"" + name + "\",\"expr\":" + expr_json_local + "}" } // Delegate to specialized boxes if ctx.starts_with_kw(src, j, "if") == 1 { - return ParserControlBox.parse_if(src, j, stmt_start, ctx) + local out_if = ParserControlBox.parse_if(src, j, stmt_start, ctx) + // Reset recursion guard before returning + env.set("HAKO_STAGEB_STMT_DEPTH", "0") + return out_if } if ctx.starts_with_kw(src, j, "loop") == 1 { - return ParserControlBox.parse_loop(src, j, stmt_start, ctx) + local out_loop = ParserControlBox.parse_loop(src, j, stmt_start, ctx) + // Reset recursion guard before returning + env.set("HAKO_STAGEB_STMT_DEPTH", "0") + return out_loop } if ctx.starts_with_kw(src, j, "break") == 1 { - return ParserControlBox.parse_break(src, j, stmt_start, ctx) + local out_break = ParserControlBox.parse_break(src, j, stmt_start, ctx) + // Reset recursion guard before returning + env.set("HAKO_STAGEB_STMT_DEPTH", "0") + return out_break } if ctx.starts_with_kw(src, j, "continue") == 1 { - return ParserControlBox.parse_continue(src, j, stmt_start, ctx) + local out_cont = ParserControlBox.parse_continue(src, j, stmt_start, ctx) + // Reset recursion guard before returning + env.set("HAKO_STAGEB_STMT_DEPTH", "0") + return out_cont } if ctx.starts_with_kw(src, j, "throw") == 1 { - return ParserExceptionBox.parse_throw(src, j, stmt_start, ctx) + local out_throw = ParserExceptionBox.parse_throw(src, j, stmt_start, ctx) + // Reset recursion guard before returning + env.set("HAKO_STAGEB_STMT_DEPTH", "0") + return out_throw } if ctx.starts_with_kw(src, j, "try") == 1 { - return ParserExceptionBox.parse_try(src, j, stmt_start, ctx) + local out_try = ParserExceptionBox.parse_try(src, j, stmt_start, ctx) + // Reset recursion guard before returning + env.set("HAKO_STAGEB_STMT_DEPTH", "0") + return out_try } // Fallback: expression or unknown token