Fix MIR builder me-call recursion and add compile tracing
This commit is contained in:
@ -1559,3 +1559,26 @@ Update (2025-11-15 — Stage1 CLI emit調査メモ)
|
|||||||
- Rust 側 JSON v0 ブリッジに `ExprV0::Null` variant を追加し、Stage‐B が吐く Null リテラルを受理。
|
- Rust 側 JSON v0 ブリッジに `ExprV0::Null` variant を追加し、Stage‐B が吐く Null リテラルを受理。
|
||||||
- `launcher.hako` の `while` を `loop` 構文へ置換(Stage‐B パーサ互換)。
|
- `launcher.hako` の `while` を `loop` 構文へ置換(Stage‐B パーサ互換)。
|
||||||
- Stage1 CLI の Program(JSON) は attr付き defs まで含むようになったが、selfhost builder (MirBuilderBox on VM)がまだ HakoCli 全体を lowering できず stub MIR を返している。provider 経由では 62KB 超の MIR(JSON) が得られるので Phase 25.1a ではこちらを利用。
|
- Stage1 CLI の Program(JSON) は attr付き defs まで含むようになったが、selfhost builder (MirBuilderBox on VM)がまだ HakoCli 全体を lowering できず stub MIR を返している。provider 経由では 62KB 超の MIR(JSON) が得られるので Phase 25.1a ではこちらを利用。
|
||||||
|
Update (2025-11-17 — Phase 25.1d: Rust MIR me-call recursion fix & Stage‑B stack overflow原因特定)
|
||||||
|
- 状況:
|
||||||
|
- `tools/test_stageb_min.sh` の Test2(`compiler_stageb.hako -- --source stageb_min_sample.hako`)で発生していた stack overflow / Segfault は、Rust MIR builder 層の無限再帰が原因だったことが判明。
|
||||||
|
- backtrace および `logs/stageb_min_full.log` から、次の 3 関数がループしていることが確認できた:
|
||||||
|
- `try_build_me_method_call`(`src/mir/builder/calls/build.rs`)
|
||||||
|
- `try_handle_me_direct_call`(`src/mir/builder/builder_calls.rs`)
|
||||||
|
- `handle_me_method_call`(`src/mir/builder/method_call_handlers.rs`)
|
||||||
|
→ `try_build_me_method_call` → `handle_me_method_call` → `try_handle_me_direct_call` → 再び `try_build_me_method_call` という三角ループで Rust スタックを消費していた。
|
||||||
|
- 対応(Rust 層):
|
||||||
|
- `src/mir/builder/calls/build.rs` の `try_build_me_method_call` から「3-a) Static box fast path」として `handle_me_method_call` を即呼び出すパスを削除し、相互再帰を解消。
|
||||||
|
- これにより `try_build_me_method_call` は「enclosing box 名を見て、既に lower 済みの `<Box>.<method>/Arity` を Global call に差し替える」経路だけを担うようになった(インスタンス Box 向けの 1 パスのみ)。
|
||||||
|
- `src/mir/builder.rs` / `src/mir/builder/lifecycle.rs` に `NYASH_MIR_COMPILE_TRACE=1` 用の compile trace を追加し、
|
||||||
|
- `lower_root` の static box 降ろし時に `[mir-compile] lower static box <Name>` をログ出力できるようにした(デフォルト OFF)。
|
||||||
|
- 観測結果(修正後):
|
||||||
|
- `NYASH_MIR_COMPILE_TRACE=1` で Test2 を再実行したところ:
|
||||||
|
- `BundleResolver` / `ParserBox` 関連の static box 群 / `StageBArgsBox` / `StageBBodyExtractorBox` / `StageBDriverBox` / `ParserStub` 等、すべての static box が `[mir-compile] lower static box ...` ログ付きで正常に MIR に lower されることを確認。
|
||||||
|
- `Main` の `build_static_main_box` も `Before/After lower_static_method_as_function` のデバッグログ付きで完走している。
|
||||||
|
- stack overflow / Segfault は消え、最終的なエラーは:
|
||||||
|
- `❌ VM error: Invalid instruction: extern function: Unknown: env.set/2`
|
||||||
|
となっており、Rust VM/MIR 層ではなく Nyash側の `env.set` extern 定義不足(Stage‑B 再入ガードに env を使っている部分)だけが残っている状態まで収束した。
|
||||||
|
- 今後のタスク(Phase 25.1d 続き):
|
||||||
|
- Stage‑B の再入ガードを `env.set/2` ではなく Box 内 state(フィールド)で持たせるか、あるいは `env.set/2` を Stage‑B 実行環境向けに Rust 側 extern として提供するかを検討・実装する。
|
||||||
|
- `NYASH_MIR_COMPILE_TRACE` ログを活用して、今後も MIR builder 側の深い再帰や相互再帰を早期に検知できるようにする(特に Stage‑B / ParserBox 周辺の me 呼び出し)。
|
||||||
|
|||||||
@ -136,6 +136,9 @@ required-features = ["gui-examples"]
|
|||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
|
||||||
|
# 開発時のスタックオーバーフロー診断用
|
||||||
|
backtrace-on-stack-overflow = "0.3"
|
||||||
|
|
||||||
# シリアライゼーション(将来のAST永続化用)
|
# シリアライゼーション(将来のAST永続化用)
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|||||||
@ -15,7 +15,7 @@ impl MirInterpreter {
|
|||||||
) -> Result<VMValue, VMError> {
|
) -> Result<VMValue, VMError> {
|
||||||
// Safety valve: cap nested exec_function_inner depth to avoid Rust stack overflow
|
// Safety valve: cap nested exec_function_inner depth to avoid Rust stack overflow
|
||||||
// on accidental infinite recursion in MIR (e.g., self-recursive call chains).
|
// on accidental infinite recursion in MIR (e.g., self-recursive call chains).
|
||||||
const MAX_CALL_DEPTH: usize = 1024;
|
const MAX_CALL_DEPTH: usize = 128;
|
||||||
self.call_depth = self.call_depth.saturating_add(1);
|
self.call_depth = self.call_depth.saturating_add(1);
|
||||||
if self.call_depth > MAX_CALL_DEPTH {
|
if self.call_depth > MAX_CALL_DEPTH {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
|
|||||||
12
src/main.rs
12
src/main.rs
@ -9,6 +9,18 @@ use nyash_rust::runner::NyashRunner;
|
|||||||
|
|
||||||
/// Thin entry point - delegates to CLI parsing and runner execution
|
/// Thin entry point - delegates to CLI parsing and runner execution
|
||||||
fn main() {
|
fn main() {
|
||||||
|
// Optional: enable backtrace on stack overflow for deep debug runs.
|
||||||
|
// Guarded by env to keep default behavior unchanged.
|
||||||
|
if std::env::var("NYASH_DEBUG_STACK_OVERFLOW")
|
||||||
|
.ok()
|
||||||
|
.as_deref()
|
||||||
|
== Some("1")
|
||||||
|
{
|
||||||
|
unsafe {
|
||||||
|
let _ = backtrace_on_stack_overflow::enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// hv1 direct (primary): earliest possible check before any bootstrap/log init
|
// hv1 direct (primary): earliest possible check before any bootstrap/log init
|
||||||
// If NYASH_VERIFY_JSON is present and route is requested, execute and exit.
|
// If NYASH_VERIFY_JSON is present and route is requested, execute and exit.
|
||||||
// This avoids plugin host/registry initialization and keeps output minimal.
|
// This avoids plugin host/registry initialization and keeps output minimal.
|
||||||
|
|||||||
@ -314,6 +314,24 @@ impl MirBuilder {
|
|||||||
self.debug_scope_stack.last().cloned()
|
self.debug_scope_stack.last().cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------
|
||||||
|
// Compile trace helpers (dev only; env-gated)
|
||||||
|
// ----------------------
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn compile_trace_enabled() -> bool {
|
||||||
|
std::env::var("NYASH_MIR_COMPILE_TRACE")
|
||||||
|
.ok()
|
||||||
|
.as_deref()
|
||||||
|
== Some("1")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn trace_compile<S: AsRef<str>>(&self, msg: S) {
|
||||||
|
if Self::compile_trace_enabled() {
|
||||||
|
eprintln!("[mir-compile] {}", msg.as_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------
|
// ----------------------
|
||||||
// Method tail index (performance helper)
|
// Method tail index (performance helper)
|
||||||
// ----------------------
|
// ----------------------
|
||||||
|
|||||||
@ -400,12 +400,7 @@ impl MirBuilder {
|
|||||||
method: &str,
|
method: &str,
|
||||||
arguments: &[ASTNode],
|
arguments: &[ASTNode],
|
||||||
) -> Result<Option<ValueId>, String> {
|
) -> Result<Option<ValueId>, String> {
|
||||||
// 3-a) Static box fast path
|
// Instance box: prefer enclosing box method
|
||||||
if let Some(res) = self.handle_me_method_call(method, arguments)? {
|
|
||||||
return Ok(Some(res));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3-b) Instance box: prefer enclosing box method
|
|
||||||
let enclosing_cls: Option<String> = self
|
let enclosing_cls: Option<String> = self
|
||||||
.current_function
|
.current_function
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|||||||
@ -87,6 +87,8 @@ impl super::MirBuilder {
|
|||||||
if name == "Main" {
|
if name == "Main" {
|
||||||
main_static = Some((name.clone(), methods.clone()));
|
main_static = Some((name.clone(), methods.clone()));
|
||||||
} else {
|
} else {
|
||||||
|
// Dev: trace which static box is being lowered (env-gated)
|
||||||
|
self.trace_compile(format!("lower static box {}", name));
|
||||||
// 🎯 箱理論: 各static boxに専用のコンパイルコンテキストを作成
|
// 🎯 箱理論: 各static boxに専用のコンパイルコンテキストを作成
|
||||||
// これにより、using文や前のboxからのメタデータ汚染を構造的に防止
|
// これにより、using文や前のboxからのメタデータ汚染を構造的に防止
|
||||||
// スコープを抜けると自動的にコンテキストが破棄される
|
// スコープを抜けると自動的にコンテキストが破棄される
|
||||||
@ -105,12 +107,12 @@ impl super::MirBuilder {
|
|||||||
.push((name.clone(), params.len()));
|
.push((name.clone(), params.len()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 🎯 箱理論: コンテキストをクリア(スコープ終了で自動破棄)
|
// 🎯 箱理論: コンテキストをクリア(スコープ終了で自動破棄)
|
||||||
// これにより、次のstatic boxは汚染されていない状態から開始される
|
// これにより、次のstatic boxは汚染されていない状態から開始される
|
||||||
self.compilation_context = None;
|
self.compilation_context = None;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Instance box: register type and lower instance methods/ctors as functions
|
// Instance box: register type and lower instance methods/ctors as functions
|
||||||
self.user_defined_boxes.insert(name.clone());
|
self.user_defined_boxes.insert(name.clone());
|
||||||
|
|||||||
Reference in New Issue
Block a user