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 リテラルを受理。
|
||||
- `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 ではこちらを利用。
|
||||
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"
|
||||
anyhow = "1.0"
|
||||
|
||||
# 開発時のスタックオーバーフロー診断用
|
||||
backtrace-on-stack-overflow = "0.3"
|
||||
|
||||
# シリアライゼーション(将来のAST永続化用)
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
@ -15,7 +15,7 @@ impl MirInterpreter {
|
||||
) -> Result<VMValue, VMError> {
|
||||
// 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).
|
||||
const MAX_CALL_DEPTH: usize = 1024;
|
||||
const MAX_CALL_DEPTH: usize = 128;
|
||||
self.call_depth = self.call_depth.saturating_add(1);
|
||||
if self.call_depth > MAX_CALL_DEPTH {
|
||||
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
|
||||
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
|
||||
// If NYASH_VERIFY_JSON is present and route is requested, execute and exit.
|
||||
// This avoids plugin host/registry initialization and keeps output minimal.
|
||||
|
||||
@ -314,6 +314,24 @@ impl MirBuilder {
|
||||
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)
|
||||
// ----------------------
|
||||
|
||||
@ -400,12 +400,7 @@ impl MirBuilder {
|
||||
method: &str,
|
||||
arguments: &[ASTNode],
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
// 3-a) Static box fast path
|
||||
if let Some(res) = self.handle_me_method_call(method, arguments)? {
|
||||
return Ok(Some(res));
|
||||
}
|
||||
|
||||
// 3-b) Instance box: prefer enclosing box method
|
||||
// Instance box: prefer enclosing box method
|
||||
let enclosing_cls: Option<String> = self
|
||||
.current_function
|
||||
.as_ref()
|
||||
|
||||
@ -87,6 +87,8 @@ impl super::MirBuilder {
|
||||
if name == "Main" {
|
||||
main_static = Some((name.clone(), methods.clone()));
|
||||
} else {
|
||||
// Dev: trace which static box is being lowered (env-gated)
|
||||
self.trace_compile(format!("lower static box {}", name));
|
||||
// 🎯 箱理論: 各static boxに専用のコンパイルコンテキストを作成
|
||||
// これにより、using文や前のboxからのメタデータ汚染を構造的に防止
|
||||
// スコープを抜けると自動的にコンテキストが破棄される
|
||||
@ -99,17 +101,17 @@ impl super::MirBuilder {
|
||||
if let N::FunctionDeclaration { params, body, .. } = mast {
|
||||
let func_name = format!("{}.{}{}", name, mname, format!("/{}", params.len()));
|
||||
self.lower_static_method_as_function(func_name, params.clone(), body.clone())?;
|
||||
self.static_method_index
|
||||
.entry(mname.clone())
|
||||
.or_insert_with(Vec::new)
|
||||
.push((name.clone(), params.len()));
|
||||
self.static_method_index
|
||||
.entry(mname.clone())
|
||||
.or_insert_with(Vec::new)
|
||||
.push((name.clone(), params.len()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 🎯 箱理論: コンテキストをクリア(スコープ終了で自動破棄)
|
||||
// これにより、次のstatic boxは汚染されていない状態から開始される
|
||||
self.compilation_context = None;
|
||||
}
|
||||
// 🎯 箱理論: コンテキストをクリア(スコープ終了で自動破棄)
|
||||
// これにより、次のstatic boxは汚染されていない状態から開始される
|
||||
self.compilation_context = None;
|
||||
}
|
||||
} else {
|
||||
// Instance box: register type and lower instance methods/ctors as functions
|
||||
|
||||
Reference in New Issue
Block a user