Files
hakorune/src/runner/modes/vm_fallback.rs
nyash-codex c6c5653ae0 fix(parser): Stage-1 CLI infinite loop - MirBuilder-friendly refactoring (Part 1/3)
**Problem**: Stage-1 CLI hits VM step budget (infinite loops) in multiple functions
- ParserBox.parse_program2: bb2853→bb2854 loop
- StringHelpers.skip_ws: bb488→bb491 loop

**Root Cause**: MirBuilder bug with `loop(cont==1)` + nested `if-else` pattern
- PHI instructions with self-references
- Loop exit blocks jumping back to header instead of continuation

**Solution**: Refactor to MirBuilder-friendly pattern
```hako
// Before (causes infinite loop):
loop(cont == 1) {
  if guard > max { return j } else { guard++ }
  if condition { action } else { cont = 0 }
}

// After (MirBuilder-friendly):
loop(j < n) {
  if condition { j++; continue }
  break
}
```

**Changes**:
1. parser_box.hako: parse_program2 main loop refactored
   - 7 sections: ws→EOF/guard→parse_stmt2→progress guard→trace→semicolons→emit
   - Unconditional parse_stmt2 execution (no nested if-else)
   - Explicit `break` for loop exits

2. string_helpers.hako: StringHelpers.skip_ws simplified
   - Removed `cont` flag and guard counter
   - Direct `loop(j < n)` with `continue/break`

3. vm.rs, vm_fallback.rs: RUST_MIR_DUMP_PATH support
   - Enables offline MIR analysis for debugging

**Progress**:
-  parse_program2: infinite loop fixed
-  StringHelpers.skip_ws: infinite loop fixed
-  ParserIdentScanBox.scan_ident: next target (bb985 loop)

**Testing**:
- MIR dumps generated successfully (116K+ lines)
- ws_init loop completes
- parse_program2 progresses further
- Still hits infinite loop in scan_ident (Part 2 needed)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-24 18:19:31 +09:00

526 lines
22 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use super::super::NyashRunner;
use crate::{
backend::MirInterpreter,
box_factory::{BoxFactory, RuntimeError},
core::model::BoxDeclaration as CoreBoxDecl,
instance_v2::InstanceBox,
mir::MirCompiler,
parser::NyashParser,
};
use std::sync::{Arc, RwLock};
use std::{fs, process};
impl NyashRunner {
/// Lightweight VM fallback using the in-crate MIR interpreter.
/// - Respects using preprocessing done earlier in the pipeline
/// - Relies on global plugin host initialized by runner
pub(crate) fn execute_vm_fallback_interpreter(&self, filename: &str) {
// Note: hv1 direct route is now handled at main.rs entry point (before plugin initialization).
// This function is only called after plugin initialization has already occurred.
// Read source
let code = match fs::read_to_string(filename) {
Ok(s) => s,
Err(e) => {
eprintln!("❌ Error reading file {}: {}", filename, e);
process::exit(1);
}
};
let trace = crate::config::env::cli_verbose()
|| crate::config::env::env_bool("NYASH_RESOLVE_TRACE");
// Unified using/prelude handling (SSOT, parity with vm.rs):
// - resolve_prelude_paths_profiled で preludes を発見
// - merge_prelude_text で text-merge.hako は AST parse しない)
let mut code2 = if crate::config::env::enable_using() {
match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(
self, &code, filename,
) {
Ok((_, prelude_paths)) => {
if !prelude_paths.is_empty() {
match crate::runner::modes::common_util::resolve::merge_prelude_text(
self, &code, filename,
) {
Ok(merged) => {
if trace {
eprintln!(
"[using/text-merge] preludes={} (vm-fallback)",
prelude_paths.len()
);
}
merged
}
Err(e) => {
eprintln!("{}", e);
process::exit(1);
}
}
} else {
code.clone()
}
}
Err(e) => {
eprintln!("{}", e);
process::exit(1);
}
}
} else {
// using disabled: detect and fail fast if present
if code.contains("\nusing ") || code.trim_start().starts_with("using ") {
eprintln!(
"❌ using: prelude merge is disabled in this profile. Enable NYASH_USING_AST=1 or remove 'using' lines."
);
process::exit(1);
}
code
};
// Dev sugar pre-expand: @name = expr → local name = expr
code2 = crate::runner::modes::common_util::resolve::preexpand_at_local(&code2);
// Hako-friendly normalize
if crate::runner::modes::common_util::hako::looks_like_hako_code(&code2)
|| filename.ends_with(".hako")
{
code2 = crate::runner::modes::common_util::hako::strip_local_decl(&code2);
}
if trace
&& (std::env::var("NYASH_PARSER_STAGE3").ok() == Some("1".into())
|| std::env::var("HAKO_PARSER_STAGE3").ok() == Some("1".into()))
{
eprintln!("[vm-fallback] Stage-3: enabled (env) for {}", filename);
}
// FailFast (optin): Hako 構文を Nyash VM 経路で実行しない
// 目的: .hako は Hakorune VM、MIR は Core/LLVM に役割分離するためのガード
{
let on = crate::runner::modes::common_util::hako::fail_fast_on_hako();
if on {
let s = code2.as_str();
let hako_like = s.contains("static box ")
|| s.contains("using selfhost.")
|| s.contains("using hakorune.");
if hako_like {
eprintln!(
"❌ Hako-like source detected in Nyash VM path. Use Hakorune VM (v1 dispatcher) or Core/LLVM for MIR.\n hint: set HAKO_VERIFY_PRIMARY=hakovm in verify path"
);
process::exit(1);
}
}
}
// Parse main code
let main_ast = match NyashParser::parse_from_string(&code2) {
Ok(ast) => ast,
Err(e) => {
crate::runner::modes::common_util::diag::print_parse_error_with_context(
filename, &code2, &e,
);
process::exit(1);
}
};
// No AST preludes (text path or no using) → use the parsed main AST as-is
let ast_combined = main_ast;
// Optional: dump AST statement kinds for quick diagnostics
if std::env::var("NYASH_AST_DUMP").ok().as_deref() == Some("1") {
use nyash_rust::ast::ASTNode;
eprintln!("[ast] dump start (vm-fallback)");
if let ASTNode::Program { statements, .. } = &ast_combined {
for (i, st) in statements.iter().enumerate().take(50) {
let kind = match st {
ASTNode::BoxDeclaration {
is_static, name, ..
} => {
if *is_static {
format!("StaticBox({})", name)
} else {
format!("Box({})", name)
}
}
ASTNode::FunctionDeclaration { name, .. } => format!("FuncDecl({})", name),
ASTNode::FunctionCall { name, .. } => format!("FuncCall({})", name),
ASTNode::MethodCall { method, .. } => format!("MethodCall({})", method),
ASTNode::ScopeBox { .. } => "ScopeBox".to_string(),
ASTNode::ImportStatement { path, .. } => format!("Import({})", path),
ASTNode::UsingStatement { namespace_name, .. } => {
format!("Using({})", namespace_name)
}
_ => format!("{:?}", st),
};
eprintln!("[ast] {}: {}", i, kind);
}
}
eprintln!("[ast] dump end");
}
let ast = crate::r#macro::maybe_expand_and_dump(&ast_combined, false);
// Minimal user-defined Box support (Option A):
// Collect BoxDeclaration entries from AST and register a lightweight
// factory into the unified registry so `new UserBox()` works on the
// VM fallback path as well.
{
use nyash_rust::ast::ASTNode;
// Collect user-defined (non-static) box declarations at program level.
// Additionally, record static box names so we can alias
// `StaticBoxName` -> `StaticBoxNameInstance` when such a
// concrete instance box exists (common pattern in libs).
let mut nonstatic_decls: std::collections::HashMap<String, CoreBoxDecl> =
std::collections::HashMap::new();
let mut static_names: Vec<String> = Vec::new();
if let ASTNode::Program { statements, .. } = &ast {
for st in statements {
if let ASTNode::BoxDeclaration {
name,
fields,
public_fields,
private_fields,
methods,
constructors,
init_fields,
weak_fields,
is_interface,
extends,
implements,
type_parameters,
is_static,
..
} = st
{
if *is_static {
static_names.push(name.clone());
continue; // modules/static boxes are not user-instantiable directly
}
let decl = CoreBoxDecl {
name: name.clone(),
fields: fields.clone(),
public_fields: public_fields.clone(),
private_fields: private_fields.clone(),
methods: methods.clone(),
constructors: constructors.clone(),
init_fields: init_fields.clone(),
weak_fields: weak_fields.clone(),
is_interface: *is_interface,
extends: extends.clone(),
implements: implements.clone(),
type_parameters: type_parameters.clone(),
};
nonstatic_decls.insert(name.clone(), decl);
}
}
}
// Build final map with optional aliases for StaticName -> StaticNameInstance
let mut decls = nonstatic_decls.clone();
for s in static_names.into_iter() {
let inst = format!("{}Instance", s);
if let Some(d) = nonstatic_decls.get(&inst) {
decls.insert(s, d.clone());
}
}
if !decls.is_empty() {
// Inline factory: minimal User factory backed by collected declarations
struct InlineUserBoxFactory {
decls: Arc<RwLock<std::collections::HashMap<String, CoreBoxDecl>>>,
}
impl BoxFactory for InlineUserBoxFactory {
fn create_box(
&self,
name: &str,
args: &[Box<dyn crate::box_trait::NyashBox>],
) -> Result<Box<dyn crate::box_trait::NyashBox>, RuntimeError>
{
let opt = { self.decls.read().unwrap().get(name).cloned() };
let decl = match opt {
Some(d) => d,
None => {
return Err(RuntimeError::InvalidOperation {
message: format!("Unknown Box type: {}", name),
})
}
};
let mut inst = InstanceBox::from_declaration(
decl.name.clone(),
decl.fields.clone(),
decl.methods.clone(),
);
let _ = inst.init(args);
Ok(Box::new(inst))
}
fn box_types(&self) -> Vec<&str> {
vec![]
}
fn is_available(&self) -> bool {
true
}
fn factory_type(&self) -> crate::box_factory::FactoryType {
crate::box_factory::FactoryType::User
}
}
let factory = InlineUserBoxFactory {
decls: Arc::new(RwLock::new(decls)),
};
crate::runtime::unified_registry::register_user_defined_factory(Arc::new(factory));
}
}
let mut compiler = MirCompiler::with_options(!self.config.no_optimize);
let compile = match crate::runner::modes::common_util::source_hint::compile_with_source_hint(
&mut compiler,
ast,
Some(filename),
) {
Ok(c) => c,
Err(e) => {
eprintln!("❌ MIR compilation error: {}", e);
process::exit(1);
}
};
// Optional barrier-elision for parity with VM path
let mut module_vm = compile.module.clone();
if crate::config::env::env_bool("NYASH_VM_ESCAPE_ANALYSIS") {
let removed = crate::mir::passes::escape::escape_elide_barriers_vm(&mut module_vm);
if removed > 0 {
crate::cli_v!(
"[VM-fallback] escape_elide_barriers: removed {} barriers",
removed
);
}
}
// Optional: dump MIR for diagnostics (parity with vm path)
// Phase 25.1: File dump for offline analysis (ParserBox等)
if let Ok(path) = std::env::var("RUST_MIR_DUMP_PATH") {
if let Ok(mut f) = std::fs::File::create(&path) {
let p = crate::mir::MirPrinter::new();
let _ = std::io::Write::write_all(&mut f, p.print_module(&module_vm).as_bytes());
eprintln!("[vm-fallback] MIR dumped to: {}", path);
}
}
// Existing: NYASH_VM_DUMP_MIR dumps to stderr
if crate::config::env::env_bool("NYASH_VM_DUMP_MIR") {
let p = crate::mir::MirPrinter::new();
eprintln!("{}", p.print_module(&module_vm));
}
// Execute via MIR interpreter
let mut vm = MirInterpreter::new();
// Centralized plugin guard (non-strict by default on fallback route)
crate::runner::modes::common_util::plugin_guard::check_and_report(
false,
crate::config::env::env_bool("NYASH_JSON_ONLY"),
"vm-fallback",
);
// Optional: verify MIR before execution (dev-only)
if crate::config::env::env_bool("NYASH_VM_VERIFY_MIR") {
let mut verifier = crate::mir::verification::MirVerifier::new();
for (name, func) in module_vm.functions.iter() {
if let Err(errors) = verifier.verify_function(func) {
if !errors.is_empty() {
eprintln!("[vm-verify] function: {}", name);
for er in errors {
eprintln!("{}", er);
}
}
}
}
}
if std::env::var("NYASH_DUMP_FUNCS").ok().as_deref() == Some("1") {
eprintln!("[vm] functions available:");
for k in module_vm.functions.keys() {
eprintln!(" - {}", k);
}
}
match vm.execute_module(&module_vm) {
Ok(ret) => {
use crate::box_trait::{BoolBox, IntegerBox};
// Extract exit code from return value
let exit_code = if let Some(ib) = ret.as_any().downcast_ref::<IntegerBox>() {
ib.value as i32
} else if let Some(bb) = ret.as_any().downcast_ref::<BoolBox>() {
if bb.value {
1
} else {
0
}
} else {
// For non-integer/bool returns, default to 0 (success)
0
};
// Exit with the return value as exit code
process::exit(exit_code);
}
Err(e) => {
eprintln!("❌ VM fallback error: {}", e);
process::exit(1);
}
}
}
}
impl NyashRunner {
/// Small helper to continue fallback execution once AST is prepared
#[allow(dead_code)]
fn execute_vm_fallback_from_ast(&self, filename: &str, ast: nyash_rust::ast::ASTNode) {
use crate::{
backend::MirInterpreter,
box_factory::{BoxFactory, RuntimeError},
core::model::BoxDeclaration as CoreBoxDecl,
instance_v2::InstanceBox,
mir::MirCompiler,
};
use std::process;
use std::sync::{Arc, RwLock};
// Macro expand (if enabled)
let ast = crate::r#macro::maybe_expand_and_dump(&ast, false);
// Minimal user-defined Box support (inline factory)
{
use nyash_rust::ast::ASTNode;
let mut nonstatic_decls: std::collections::HashMap<String, CoreBoxDecl> =
std::collections::HashMap::new();
let mut static_names: Vec<String> = Vec::new();
if let ASTNode::Program { statements, .. } = &ast {
for st in statements {
if let ASTNode::BoxDeclaration {
name,
fields,
public_fields,
private_fields,
methods,
constructors,
init_fields,
weak_fields,
is_interface,
extends,
implements,
type_parameters,
is_static,
..
} = st
{
if *is_static {
static_names.push(name.clone());
continue;
}
let decl = CoreBoxDecl {
name: name.clone(),
fields: fields.clone(),
public_fields: public_fields.clone(),
private_fields: private_fields.clone(),
methods: methods.clone(),
constructors: constructors.clone(),
init_fields: init_fields.clone(),
weak_fields: weak_fields.clone(),
is_interface: *is_interface,
extends: extends.clone(),
implements: implements.clone(),
type_parameters: type_parameters.clone(),
};
nonstatic_decls.insert(name.clone(), decl);
}
}
}
let mut decls = nonstatic_decls.clone();
for s in static_names.into_iter() {
let inst = format!("{}Instance", s);
if let Some(d) = nonstatic_decls.get(&inst) {
decls.insert(s, d.clone());
}
}
if !decls.is_empty() {
struct InlineUserBoxFactory {
decls: Arc<RwLock<std::collections::HashMap<String, CoreBoxDecl>>>,
}
impl BoxFactory for InlineUserBoxFactory {
fn create_box(
&self,
name: &str,
args: &[Box<dyn crate::box_trait::NyashBox>],
) -> Result<Box<dyn crate::box_trait::NyashBox>, RuntimeError>
{
let opt = { self.decls.read().unwrap().get(name).cloned() };
let decl = match opt {
Some(d) => d,
None => {
return Err(RuntimeError::InvalidOperation {
message: format!("Unknown Box type: {}", name),
})
}
};
let mut inst = InstanceBox::from_declaration(
decl.name.clone(),
decl.fields.clone(),
decl.methods.clone(),
);
let _ = inst.init(args);
Ok(Box::new(inst))
}
fn box_types(&self) -> Vec<&str> {
vec![]
}
fn is_available(&self) -> bool {
true
}
fn factory_type(&self) -> crate::box_factory::FactoryType {
crate::box_factory::FactoryType::User
}
}
let factory = InlineUserBoxFactory {
decls: Arc::new(RwLock::new(decls)),
};
crate::runtime::unified_registry::register_user_defined_factory(Arc::new(factory));
}
}
// Compile to MIR and execute via interpreter
let mut compiler = MirCompiler::with_options(!self.config.no_optimize);
let module = match crate::runner::modes::common_util::source_hint::compile_with_source_hint(
&mut compiler,
ast,
Some(filename),
) {
Ok(r) => r.module,
Err(e) => {
eprintln!("❌ MIR compilation error: {}", e);
process::exit(1);
}
};
let mut interp = MirInterpreter::new();
match interp.execute_module(&module) {
Ok(result) => {
use nyash_rust::box_trait::{BoolBox, IntegerBox};
let rc = if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
ib.value as i32
} else if let Some(bb) = result.as_any().downcast_ref::<BoolBox>() {
if bb.value {
1
} else {
0
}
} else {
0
};
// For CAPI pure pipeline, suppress "RC:" text to keep last line = exe path
let capi = std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() == Some("1");
let pure = std::env::var("HAKO_CAPI_PURE").ok().as_deref() == Some("1");
if !(capi && pure) {
println!("RC: {}", rc);
}
process::exit(rc);
}
Err(e) => {
eprintln!("❌ VM fallback runtime error: {}", e);
process::exit(1);
}
}
}
}