Files
hakorune/src/runner/modes/vm.rs
nyash-codex 7bcb7ec596 feat(runtime): Phase 98 ConsoleService 代表パス拡張 - 7箇所置き換え完了
## Phase 98 完了項目
-  println!/eprintln! 7箇所 → ConsoleService 経由に移行
-  console_println! マクロ追加(Graceful Degradation パターン)
-  try_get_core_plugin_host() 追加(安全なアクセサー)
-  全テストPASS(core_services: 11, plugin_host: 7)

## 置き換え箇所(7箇所)
**selfhost/child.rs** (3箇所):
- spawn失敗エラー
- タイムアウトメッセージ(stdout/stderr)

**core_bridge.rs** (2箇所):
- DUMP書き込みエラー
- DUMP_MUT書き込みエラー

**vm.rs** (1箇所):
- RC(return code)出力

**selfhost/json.rs** (2箇所, オプション達成):
- PyVM MIR JSON emit エラー
- PyVM 使用ログ(verbose時)

## 技術的成果
**Graceful Degradation パターン確立**:
- PluginHost 初期化前: eprintln! フォールバック
- PluginHost 初期化後: ConsoleService 使用(Ring0直結)
- Fail-Fast原則との整合性: 出力先選択のみ動的

**実装インフラ**:
- src/runtime/mod.rs: console_println! マクロ & try_get_core_plugin_host()
- 既存の get_core_plugin_host() は panic! 保持(Fail-Fast)

## 統計
- 置き換え完了: 7箇所(全体の約2%)
- 残り候補: 約359箇所(Phase 99以降)
- テスト: ビルド成功、全ユニットテストPASS

## ドキュメント
- docs/development/current/main/core_boxes_design.md: Section 15 追加(128行)
  - 実装パターン、設計判断、テスト結果を完全記録

## Phase 85-98 総括
- Phase 85-94: 構造設計 & 箱化モジュール化
- Phase 95.5: StringService/ConsoleService(Ring0直結型・純粋関数型)
- Phase 96-96.5: ArrayService/MapService(downcast型)& コード整理
- Phase 97: IntegerService/BoolService(純粋関数型、#[allow(dead_code)] 根絶)
- Phase 98: ConsoleService 実用拡大(7箇所) 完了

次: Phase 99(CoreServices 完全統合、残り約359箇所の段階的移行)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 11:04:58 +09:00

623 lines
29 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 nyash_rust::{ast::ASTNode, mir::MirCompiler, parser::NyashParser};
use std::{fs, process};
// Phase 30 F-4.4: JoinIR VM Bridge dispatch (experimental)
// Used only when NYASH_JOINIR_EXPERIMENT=1 AND NYASH_JOINIR_VM_BRIDGE=1
use crate::mir::join_ir_vm_bridge_dispatch::try_run_joinir_vm_bridge;
impl NyashRunner {
/// Execute VM mode with full plugin initialization and AST prelude merge
pub(crate) fn execute_vm_mode(&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.
// Quiet mode for child pipelines (e.g., selfhost compiler JSON emit)
let quiet_pipe = crate::config::env::env_bool("NYASH_JSON_ONLY");
if std::env::var("NYASH_EMIT_MIR_TRACE").ok().as_deref() == Some("1") {
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.debug(&format!(
"[runner/vm] entry file={} quiet_pipe={} mode=stage-b?{}",
filename,
quiet_pipe,
std::env::var("HAKO_STAGEB_TRACE")
.ok()
.unwrap_or_else(|| "0".to_string())
));
}
// Enforce plugin-first policy for VM on this branch (deterministic):
// - Initialize plugin host if not yet loaded
// - Prefer plugin implementations for core boxes
// - Optionally fail fast when plugins are missing (NYASH_VM_PLUGIN_STRICT=1)
{
// FileBox provider initialization (SSOT via ProviderFactory)
use crate::runner::modes::common_util::provider_registry;
use nyash_rust::runtime::provider_lock;
// Register builtin FileBox factory (idempotent)
#[cfg(feature = "builtin-filebox")]
{
nyash_rust::boxes::file::builtin_factory::register_builtin_filebox();
}
// Select provider based on mode (dynamic → builtin → core-ro)
let filebox_mode = provider_registry::read_filebox_mode_from_env();
let filebox_provider = provider_registry::select_file_provider(filebox_mode);
if let Err(e) = provider_lock::set_filebox_provider(filebox_provider) {
if !quiet_pipe {
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.warn(&format!("[warn] FileBox provider already set: {}", e));
}
}
// Initialize unified registry globals (idempotent)
nyash_rust::runtime::init_global_unified_registry();
// Init plugin host from nyash.toml if not yet loaded
let need_init = {
let host = nyash_rust::runtime::get_global_plugin_host();
host.read()
.map(|h| h.config_ref().is_none())
.unwrap_or(true)
};
if need_init {
// Let init_bid_plugins resolve hakorune.toml/nyash.toml and configure
crate::runner_plugin_init::init_bid_plugins();
}
// Phase 21.4: Deprecation warnings for old ENV variables
// No longer auto-setting NYASH_USE_PLUGIN_BUILTINS or NYASH_PLUGIN_OVERRIDE_TYPES
// Use NYASH_BOX_FACTORY_POLICY and NYASH_FILEBOX_MODE instead
if std::env::var("NYASH_USE_PLUGIN_BUILTINS").is_ok() {
if !quiet_pipe {
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.warn("[vm] warn: NYASH_USE_PLUGIN_BUILTINS is deprecated. Use NYASH_BOX_FACTORY_POLICY instead.");
}
}
if std::env::var("NYASH_PLUGIN_OVERRIDE_TYPES").is_ok() {
if !quiet_pipe {
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.warn("[vm] warn: NYASH_PLUGIN_OVERRIDE_TYPES is deprecated. Use NYASH_BOX_FACTORY_POLICY instead.");
}
}
// Centralized plugin guard
let strict = crate::config::env::env_bool("NYASH_VM_PLUGIN_STRICT");
crate::runner::modes::common_util::plugin_guard::check_and_report(
strict, quiet_pipe, "vm",
);
}
// Read the file
let code = match fs::read_to_string(filename) {
Ok(content) => content,
Err(e) => {
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.error(&format!("❌ Error reading file {}: {}", filename, e));
process::exit(1);
}
};
// Unified using/prelude handling (SSOT):
// - resolve_prelude_paths_profiled: discover preludes (DFS, operator boxes, etc.)
// - merge_prelude_text: text-based merge for all VM paths
// * .hako プレリュードは AST に突っ込まないParse error防止
// * .hako は text-merge + Stage-3 parser で一貫処理
let trace = crate::config::env::cli_verbose()
|| crate::config::env::env_bool("NYASH_RESOLVE_TRACE");
// When using is enabled, resolve preludes/profile; otherwise, keep original code.
let mut code_final = 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() {
// SSOT: always text-merge for VM (includes .hako-safe handling inside)
match crate::runner::modes::common_util::resolve::merge_prelude_text(
self, &code, filename,
) {
Ok(merged) => {
if trace {
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.debug(&format!(
"[using/text-merge] preludes={} (vm)",
prelude_paths.len()
));
}
merged
}
Err(e) => {
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.error(&format!("{}", e));
process::exit(1);
}
}
} else {
code.clone()
}
}
Err(e) => {
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.error(&format!("{}", e));
process::exit(1);
}
}
} else {
// using disabled: detect and fail fast if present
if code.contains("\nusing ") || code.trim_start().starts_with("using ") {
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.error("❌ 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
code_final = crate::runner::modes::common_util::resolve::preexpand_at_local(&code_final);
// Hako-friendly normalize: strip leading `local ` at line head for Nyash parser compatibility.
if crate::runner::modes::common_util::hako::looks_like_hako_code(&code_final)
|| filename.ends_with(".hako")
{
code_final = crate::runner::modes::common_util::hako::strip_local_decl(&code_final);
}
// Optional: dump merged Hako source after using/prelude merge and Hako normalization.
// Guarded by env; defaultはOFFPhase 25.1a selfhost builder デバッグ用)。
if std::env::var("NYASH_VM_DUMP_MERGED_HAKO").ok().as_deref() == Some("1") {
let default_path = {
let mut tmp = std::env::temp_dir();
tmp.push("nyash_merged_vm.hako");
tmp
};
let path = std::env::var("NYASH_VM_DUMP_MERGED_HAKO_PATH")
.ok()
.filter(|p| !p.is_empty())
.unwrap_or_else(|| default_path.to_string_lossy().into_owned());
if let Err(e) = fs::write(&path, &code_final) {
if trace {
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.warn(&format!("[vm/merged-hako] failed to write {}: {}", path, e));
}
} else if trace || crate::config::env::env_bool("NYASH_VM_DUMP_MERGED_HAKO_LOG") {
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.debug(&format!("[vm/merged-hako] dumped merged code to {}", path));
}
}
if trace && crate::config::env::parser_stage3_enabled() {
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.debug(&format!(
"[vm] Stage-3: enabled (NYASH_FEATURES/legacy 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 hako_like = code_final.contains("static box ")
|| code_final.contains("using selfhost.")
|| code_final.contains("using hakorune.");
if hako_like {
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.error("❌ 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 (after text-merge and Hako normalization)
let ast_combined = match NyashParser::parse_from_string(&code_final) {
Ok(ast) => ast,
Err(e) => {
crate::runner::modes::common_util::diag::print_parse_error_with_context(
filename,
&code_final,
&e,
);
// Enhanced context: list merged prelude files if any
let preludes =
crate::runner::modes::common_util::resolve::clone_last_merged_preludes();
if !preludes.is_empty() {
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.debug(&format!("[parse/context] merged prelude files ({}):", preludes.len()));
let show = std::cmp::min(16, preludes.len());
for p in preludes.iter().take(show) {
ring0.log.debug(&format!(" - {}", p));
}
if preludes.len() > show {
ring0.log.debug(&format!(" ... ({} more)", preludes.len() - show));
}
}
process::exit(1);
}
};
// Optional: dump AST statement kinds for quick diagnostics
if std::env::var("NYASH_AST_DUMP").ok().as_deref() == Some("1") {
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.debug("[ast] dump start (vm)");
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),
};
ring0.log.debug(&format!("[ast] {}: {}", i, kind));
}
}
ring0.log.debug("[ast] dump end");
}
// Macro expand (if enabled)
let ast = crate::r#macro::maybe_expand_and_dump(&ast_combined, false);
// Minimal user-defined Box support (inline factory)
let static_box_decls = {
use crate::{
box_factory::{BoxFactory, RuntimeError},
core::model::BoxDeclaration as CoreBoxDecl,
instance_v2::InstanceBox,
};
use std::sync::{Arc, RwLock};
// 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).
// Also collect static box declarations for VM singleton persistence.
let mut nonstatic_decls: std::collections::HashMap<String, CoreBoxDecl> =
std::collections::HashMap::new();
let mut static_names: Vec<String> = Vec::new();
let mut static_box_decls: std::collections::HashMap<String, CoreBoxDecl> =
std::collections::HashMap::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());
// Store static box declaration for VM singleton persistence
let static_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(),
};
static_box_decls.insert(name.clone(), static_decl);
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);
}
}
}
// Phase 25.1b: Include static boxes in user factory for NewBox support
// Static boxes are singletons (via VM's register_static_box_decl) but must also
// be advertised by user factory so NewBox doesn't fall through to plugin route.
//
// Build final map:
// 1. Start with nonstatic_decls (regular user boxes)
// 2. Add static_box_decls (for NewBox support, VM will ensure singleton)
// 3. Add StaticName -> StaticNameInstance aliases if present
let mut decls = nonstatic_decls.clone();
// Add static box declarations (VM singleton instances)
// nonstatic takes precedence if name conflicts (rare but possible)
for (name, sdecl) in static_box_decls.iter() {
if !decls.contains_key(name) {
decls.insert(name.clone(), sdecl.clone());
}
}
// Add StaticName -> StaticNameInstance aliases if present
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 guard = self.decls.read().unwrap();
let opt = guard.get(name).cloned();
let decl = match opt {
Some(d) => {
drop(guard);
d
}
None => {
// Quick Win 1: Show available boxes for easier debugging
let mut available: Vec<_> = guard.keys().cloned().collect();
available.sort();
drop(guard);
let hint = if available.is_empty() {
"No user-defined boxes available".to_string()
} else if available.len() <= 10 {
format!("Available: {}", available.join(", "))
} else {
format!(
"Available ({} boxes): {}, ...",
available.len(),
available[..10].join(", ")
)
};
return Err(RuntimeError::InvalidOperation {
message: format!("Unknown Box type: {}. {}", name, hint),
});
}
};
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> {
// Can't return borrowed strings from temporary RwLock guard
// Registry will try create_box() regardless of this list
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(
std::sync::Arc::new(factory),
);
}
// Return static_box_decls for VM registration
static_box_decls
};
// Compile to MIR
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) => {
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.error(&format!("❌ MIR compilation error: {}", e));
process::exit(1);
}
};
// Optional barrier-elision for parity with fallback 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] escape_elide_barriers: removed {} barriers", removed);
}
}
// Optional: dump MIR for diagnostics
// 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());
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.info(&format!("[vm] 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();
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.debug(&p.print_module(&module_vm));
}
// Execute via MIR interpreter
use crate::backend::MirInterpreter;
let mut vm = MirInterpreter::new();
// Register static box declarations for singleton persistence
for (name, decl) in static_box_decls {
vm.register_static_box_decl(name, decl);
}
// Optional: verify MIR before execution (dev-only)
if crate::config::env::env_bool("NYASH_VM_VERIFY_MIR") {
let ring0 = crate::runtime::ring0::get_global_ring0();
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() {
ring0.log.warn(&format!("[vm-verify] function: {}", name));
for er in errors {
ring0.log.warn(&format!("{}", er));
}
}
}
}
}
if std::env::var("NYASH_DUMP_FUNCS").ok().as_deref() == Some("1") {
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.debug("[vm] functions available:");
for k in module_vm.functions.keys() {
ring0.log.debug(&format!(" - {}", k));
}
}
// Phase 33-10.0: If lowering ドライラン統合(箱化版)
// JoinIR dev + IfSelect 有効時に IfLoweringDryRunner を使用
if crate::config::env::joinir_dev_enabled()
&& crate::config::env::joinir_if_select_enabled()
{
let debug_level = crate::config::env::joinir_debug_level();
let runner =
crate::mir::join_ir::lowering::if_dry_runner::IfLoweringDryRunner::new(debug_level);
let stats = runner.scan_module(&module_vm.functions);
runner.print_stats(&stats);
}
// Phase 30 F-4.4: JoinIR VM Bridge experimental path (consolidated dispatch)
// Activated when NYASH_JOINIR_EXPERIMENT=1 AND NYASH_JOINIR_VM_BRIDGE=1
// Routing logic is centralized in join_ir_vm_bridge_dispatch module
try_run_joinir_vm_bridge(&module_vm, quiet_pipe);
if std::env::var("NYASH_EMIT_MIR_TRACE")
.ok()
.as_deref()
== Some("1")
{
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.debug("[runner/vm] calling execute_module");
}
match vm.execute_module(&module_vm) {
Ok(ret) => {
use crate::box_trait::{BoolBox, IntegerBox};
if std::env::var("NYASH_EMIT_MIR_TRACE")
.ok()
.as_deref()
== Some("1")
{
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.debug(&format!("[runner/vm] vm_result={}", ret.to_string_box().value));
}
// 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
};
// Optional: print lightweight VM counters for diagnostics
if crate::config::env::env_bool("NYASH_VM_STATS") {
let ring0 = crate::runtime::ring0::get_global_ring0();
let (inst, br, cmp) = vm.stats_counters();
ring0.log.debug(&format!("[vm/stats] inst={} compare={} branch={}", inst, cmp, br));
}
// Quiet mode: suppress "RC:" output for JSON-only pipelines
if !quiet_pipe {
// Phase 98: ConsoleService if available, otherwise println
if let Some(host) = crate::runtime::try_get_core_plugin_host() {
host.core.console.println(&format!("RC: {}", exit_code));
} else {
println!("RC: {}", exit_code);
}
}
if std::env::var("NYASH_EMIT_MIR_TRACE")
.ok()
.as_deref()
== Some("1")
{
let ring0 = crate::runtime::ring0::get_global_ring0();
ring0.log.debug(&format!("[runner/vm] exit_code={}", exit_code));
}
// Exit with the return value as exit code
process::exit(exit_code);
}
Err(e) => {
let ring0 = crate::runtime::ring0::get_global_ring0();
if std::env::var("NYASH_EMIT_MIR_TRACE")
.ok()
.as_deref()
== Some("1")
{
ring0.log.debug(&format!("[runner/vm] vm_error={}", e));
}
ring0.log.error(&format!("❌ [rust-vm] VM error: {}", e));
process::exit(1);
}
}
}
}