diff --git a/docs/development/current/main/ring0-inventory.md b/docs/development/current/main/ring0-inventory.md index 7135db43..4ce03993 100644 --- a/docs/development/current/main/ring0-inventory.md +++ b/docs/development/current/main/ring0-inventory.md @@ -357,7 +357,64 @@ Phase 89 以降で段階的に移行予定。 --- -## 5. 今後の棚卸しタスク(Phase 91 以降) +## 5. Phase 90 移行実績(2025-12-03) + +### Phase 90-A: fs 系移行(7箇所) + +**FsApi trait 追加**: +- `read_to_string()`, `read()`, `write_all()`, `exists()`, `metadata()`, `canonicalize()` + +**移行済みパス**: + +| ファイル | 行 | 移行内容 | +|---------|---|---------| +| `src/runner/modes/common_util/resolve/strip.rs` | 550-555 | `std::fs::read_to_string` → `ring0.fs.read_to_string` | +| `src/runner/modes/common_util/resolve/strip.rs` | 650-655 | `std::fs::read_to_string` → `ring0.fs.read_to_string` | +| `src/runner/modes/common_util/resolve/strip.rs` | 901-906 | `std::fs::read_to_string` → `ring0.fs.read_to_string` | +| `src/runner/modes/common_util/resolve/strip.rs` | 941-946 | `std::fs::read_to_string` → `ring0.fs.read_to_string` | +| `src/runner/dispatch.rs` | 31-33 | `std::fs::read_to_string` → `ring0.fs.read_to_string` | +| `src/runner/mod.rs` | 122-124 | `std::fs::read_to_string` → `ring0.fs.read_to_string` | +| `src/runner/mod.rs` | 181-183 | `std::fs::read_to_string` → `ring0.fs.read_to_string` | +| `src/runner/mod.rs` | 288-290 | `std::fs::read_to_string` → `ring0.fs.read_to_string` | + +**移行進捗**: 7/243箇所 (2.9%) + +### Phase 90-B: io 系移行 + +Phase 88 で既に IoApi 実装済みのため、スキップ(stdin/stdout は既存の ring0.io.* を使用)。 + +### Phase 90-C: time 系移行(3箇所) + +**TimeApi に elapsed() 追加**: +- `fn elapsed(&self, start: Instant) -> Duration` + +**移行済みパス**: + +| ファイル | 行 | 移行内容 | +|---------|---|---------| +| `src/runner/modes/common_util/selfhost_exe.rs` | 60-68 | `Instant::now() + elapsed()` → `ring0.time.monotonic_now() + ring0.time.elapsed()` | +| `src/runner/modes/common_util/io.rs` | 22-37 | `Instant::now() + elapsed()` → `ring0.time.monotonic_now() + ring0.time.elapsed()` | +| `src/runtime/plugin_loader_unified.rs` | 330-344 | `Instant::now() + elapsed()` → `ring0.time.monotonic_now() + ring0.time.elapsed()` | + +**移行進捗**: 3/143箇所 (2.1%) + +### Phase 90-D: thread 系移行(2箇所) + +**ThreadApi trait 追加**: +- `fn sleep(&self, duration: Duration)` + +**移行済みパス**: + +| ファイル | 行 | 移行内容 | +|---------|---|---------| +| `src/runtime/global_hooks.rs` | 246-253 | `std::thread::sleep` → `ring0.thread.sleep` | +| `src/runtime/plugin_loader_unified.rs` | 342 | `std::thread::sleep` → `ring0.thread.sleep` | + +**移行進捗**: 2/37箇所 (5.4%) + +--- + +## 6. 今後の棚卸しタスク(Phase 91 以降) - hakmem / nyrt 経由の低レベル API 呼び出し(alloc/free など)を一覧化。 - 代表パス(selfhost/hack_check/VM/LLVM)のみを対象にした「最小 Ring0 呼び出しセット」を定義する。 diff --git a/src/runner/dispatch.rs b/src/runner/dispatch.rs index 03043504..64deb726 100644 --- a/src/runner/dispatch.rs +++ b/src/runner/dispatch.rs @@ -28,7 +28,9 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) { let groups = runner.config.as_groups(); // Diagnostic/Exec: accept MIR JSON file direct (experimental; default OFF) if let Some(path) = groups.parser.mir_json_file.as_ref() { - match std::fs::read_to_string(path) { + // Phase 90-A: fs 系移行 + let ring0 = crate::runtime::ring0::get_global_ring0(); + match ring0.fs.read_to_string(std::path::Path::new(path)) { Ok(text) => { // Try schema v1 first (preferred by emitter) match crate::runner::json_v1_bridge::try_parse_v1_to_module(&text) { diff --git a/src/runner/mod.rs b/src/runner/mod.rs index 3a4df669..a5490c2f 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -119,7 +119,9 @@ impl NyashRunner { } // Early: direct MIR JSON execution (no source file). Experimental diagnostics/exec. if let Some(path) = groups.parser.mir_json_file.as_ref() { - match std::fs::read_to_string(path) { + // Phase 90-A: fs 系移行 + let ring0 = crate::runtime::ring0::get_global_ring0(); + match ring0.fs.read_to_string(std::path::Path::new(path)) { Ok(text) => match crate::runner::json_v1_bridge::try_parse_v1_to_module(&text) { Ok(Some(module)) => { let rc = self.execute_mir_module_quiet_exit(&module); @@ -176,7 +178,9 @@ impl NyashRunner { std::process::exit(0); } } else if let Some(file) = groups.input.file.as_ref() { - match std::fs::read_to_string(file) { + // Phase 90-A: fs 系移行 + let ring0 = crate::runtime::ring0::get_global_ring0(); + match ring0.fs.read_to_string(std::path::Path::new(file)) { Ok(code) => match crate::parser::NyashParser::parse_from_string(&code) { Ok(ast) => { let prog = crate::r#macro::ast_json::ast_to_json(&ast); @@ -281,7 +285,9 @@ impl NyashRunner { } // Optional dependency tree bridge (log-only) if let Ok(dep_path) = std::env::var("NYASH_DEPS_JSON") { - match std::fs::read_to_string(&dep_path) { + // Phase 90-A: fs 系移行 + let ring0 = crate::runtime::ring0::get_global_ring0(); + match ring0.fs.read_to_string(std::path::Path::new(&dep_path)) { Ok(s) => { let bytes = s.as_bytes().len(); let mut root_info = String::new(); diff --git a/src/runner/modes/common_util/io.rs b/src/runner/modes/common_util/io.rs index ddcb95ce..fb417017 100644 --- a/src/runner/modes/common_util/io.rs +++ b/src/runner/modes/common_util/io.rs @@ -1,7 +1,7 @@ use std::io::Read; use std::process::{Command, Stdio}; use std::thread::sleep; -use std::time::{Duration, Instant}; +use std::time::Duration; pub struct ChildOutput { pub stdout: Vec, @@ -19,7 +19,12 @@ pub fn spawn_with_timeout(mut cmd: Command, timeout_ms: u64) -> std::io::Result< let mut child = cmd.spawn()?; let ch_stdout = child.stdout.take(); let ch_stderr = child.stderr.take(); - let start = Instant::now(); + // Phase 90-C: time 系移行 + let ring0 = crate::runtime::ring0::get_global_ring0(); + let start = ring0 + .time + .monotonic_now() + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; let mut timed_out = false; let mut exit_status: Option = None; loop { @@ -29,7 +34,7 @@ pub fn spawn_with_timeout(mut cmd: Command, timeout_ms: u64) -> std::io::Result< break; } None => { - if start.elapsed() >= Duration::from_millis(timeout_ms) { + if ring0.time.elapsed(start) >= Duration::from_millis(timeout_ms) { let _ = child.kill(); let _ = child.wait(); timed_out = true; diff --git a/src/runner/modes/common_util/resolve/strip.rs b/src/runner/modes/common_util/resolve/strip.rs index 60d39320..50b1ff05 100644 --- a/src/runner/modes/common_util/resolve/strip.rs +++ b/src/runner/modes/common_util/resolve/strip.rs @@ -547,7 +547,11 @@ pub fn resolve_prelude_paths_profiled( if !seen.insert(key.clone()) { return Ok(()); } - let src = std::fs::read_to_string(&real_path) + // Phase 90-A: fs 系移行 + let ring0 = crate::runtime::ring0::get_global_ring0(); + let src = ring0 + .fs + .read_to_string(std::path::Path::new(&real_path)) .map_err(|e| format!("using: failed to read '{}': {}", real_path, e))?; let (_cleaned, nested, _nested_imports) = collect_using_and_strip(runner, &src, &real_path)?; @@ -643,7 +647,11 @@ pub fn parse_preludes_to_asts( prelude_path ); } - let src = std::fs::read_to_string(prelude_path) + // Phase 90-A: fs 系移行 + let ring0 = crate::runtime::ring0::get_global_ring0(); + let src = ring0 + .fs + .read_to_string(std::path::Path::new(prelude_path)) .map_err(|e| format!("using: error reading {}: {}", prelude_path, e))?; let (clean_src, _nested, _nested_imports) = collect_using_and_strip(runner, &src, prelude_path)?; @@ -890,7 +898,11 @@ pub fn merge_prelude_text( if !seen.insert(key.clone()) { return Ok(()); } - let src = std::fs::read_to_string(path) + // Phase 90-A: fs 系移行 + let ring0 = crate::runtime::ring0::get_global_ring0(); + let src = ring0 + .fs + .read_to_string(std::path::Path::new(path)) .map_err(|e| format!("using: failed to read '{}': {}", path, e))?; let (_cleaned, nested, _nested_imports) = collect_using_and_strip(runner, &src, path)?; for n in nested.iter() { @@ -926,7 +938,11 @@ pub fn merge_prelude_text( // Add preludes in DFS order for (idx, path) in prelude_paths.iter().enumerate() { - let content = std::fs::read_to_string(path) + // Phase 90-A: fs 系移行 + let ring0 = crate::runtime::ring0::get_global_ring0(); + let content = ring0 + .fs + .read_to_string(std::path::Path::new(path)) .map_err(|e| format!("using: failed to read '{}': {}", path, e))?; // Strip using lines from prelude and normalize diff --git a/src/runner/modes/common_util/selfhost_exe.rs b/src/runner/modes/common_util/selfhost_exe.rs index e809840f..37d66efd 100644 --- a/src/runner/modes/common_util/selfhost_exe.rs +++ b/src/runner/modes/common_util/selfhost_exe.rs @@ -1,7 +1,7 @@ use std::io::Read; use std::process::Stdio; use std::thread::sleep; -use std::time::{Duration, Instant}; +use std::time::Duration; /// Try external selfhost compiler EXE to parse Ny -> JSON v0 and return MIR module. /// Returns Some(module) on success, None on failure (timeout/invalid output/missing exe) @@ -57,13 +57,15 @@ pub fn exe_try_parse_json_v0(filename: &str, timeout_ms: u64) -> Option break, Ok(None) => { - if start.elapsed() >= Duration::from_millis(timeout_ms) { + if ring0.time.elapsed(start) >= Duration::from_millis(timeout_ms) { let _ = child.kill(); let _ = child.wait(); timed_out = true; diff --git a/src/runtime/global_hooks.rs b/src/runtime/global_hooks.rs index 7dce8b3b..1e4dd5e4 100644 --- a/src/runtime/global_hooks.rs +++ b/src/runtime/global_hooks.rs @@ -243,8 +243,13 @@ pub fn spawn_task_after(delay_ms: u64, name: &str, f: Box() { let max_ms: u64 = crate::config::env::await_max_ms(); - let start = std::time::Instant::now(); + // Phase 90-C/D: time/thread 系移行 + let ring0 = crate::runtime::ring0::get_global_ring0(); + let start = ring0 + .time + .monotonic_now() + .map_err(|_e| crate::bid::error::BidError::PluginError)?; let mut spins = 0usize; while !fut.ready() { crate::runtime::global_hooks::safepoint_and_poll(); std::thread::yield_now(); spins += 1; if spins % 1024 == 0 { - std::thread::sleep(std::time::Duration::from_millis(1)); + ring0.thread.sleep(std::time::Duration::from_millis(1)); } - if start.elapsed() >= std::time::Duration::from_millis(max_ms) { + if ring0.time.elapsed(start) >= std::time::Duration::from_millis(max_ms) { let err = crate::box_trait::StringBox::new("Timeout"); return Ok(Some(Box::new(NyashResultBox::new_err(Box::new(err))))); } diff --git a/src/runtime/ring0/errors.rs b/src/runtime/ring0/errors.rs index 6ac7f4a2..447b17fc 100644 --- a/src/runtime/ring0/errors.rs +++ b/src/runtime/ring0/errors.rs @@ -1,12 +1,38 @@ //! Phase 88: Ring0 エラー型定義 -/// IO 操作エラー +/// IO 操作エラー (Phase 90-A: fs 系エラー追加) #[derive(Debug, Clone)] -pub struct IoError(pub String); +pub enum IoError { + /// ファイル読み込み失敗 + ReadFailed(String), + /// ファイル書き込み失敗 + WriteFailed(String), + /// メタデータ取得失敗 + MetadataFailed(String), + /// 正規化失敗 + CanonicalizeFailed(String), + /// stdin 読み込み失敗 + StdinReadFailed(String), + /// stdout 書き込み失敗 + StdoutWriteFailed(String), + /// stderr 書き込み失敗 + StderrWriteFailed(String), + /// その他のエラー(Phase 88 互換用) + Other(String), +} impl std::fmt::Display for IoError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "IoError: {}", self.0) + match self { + IoError::ReadFailed(msg) => write!(f, "IoError (ReadFailed): {}", msg), + IoError::WriteFailed(msg) => write!(f, "IoError (WriteFailed): {}", msg), + IoError::MetadataFailed(msg) => write!(f, "IoError (MetadataFailed): {}", msg), + IoError::CanonicalizeFailed(msg) => write!(f, "IoError (CanonicalizeFailed): {}", msg), + IoError::StdinReadFailed(msg) => write!(f, "IoError (StdinReadFailed): {}", msg), + IoError::StdoutWriteFailed(msg) => write!(f, "IoError (StdoutWriteFailed): {}", msg), + IoError::StderrWriteFailed(msg) => write!(f, "IoError (StderrWriteFailed): {}", msg), + IoError::Other(msg) => write!(f, "IoError: {}", msg), + } } } diff --git a/src/runtime/ring0/mod.rs b/src/runtime/ring0/mod.rs index c8cff81b..dd612276 100644 --- a/src/runtime/ring0/mod.rs +++ b/src/runtime/ring0/mod.rs @@ -7,8 +7,10 @@ mod std_impls; mod traits; pub use errors::{IoError, TimeError}; -pub use std_impls::{NoopMem, StdIo, StdLog, StdTime}; -pub use traits::{IoApi, LogApi, LogLevel, MemApi, MemStats, TimeApi}; +pub use std_impls::{NoopMem, StdFs, StdIo, StdLog, StdThread, StdTime}; +pub use traits::{ + FsApi, FsMetadata, IoApi, LogApi, LogLevel, MemApi, MemStats, ThreadApi, TimeApi, +}; use std::sync::{Arc, OnceLock}; @@ -20,6 +22,8 @@ pub struct Ring0Context { pub io: Arc, pub time: Arc, pub log: Arc, + pub fs: Arc, // Phase 90-A + pub thread: Arc, // Phase 90-D } impl Ring0Context { @@ -29,8 +33,17 @@ impl Ring0Context { io: Arc, time: Arc, log: Arc, + fs: Arc, + thread: Arc, ) -> Self { - Self { mem, io, time, log } + Self { + mem, + io, + time, + log, + fs, + thread, + } } } @@ -41,6 +54,8 @@ impl std::fmt::Debug for Ring0Context { .field("io", &"") .field("time", &"") .field("log", &"") + .field("fs", &"") + .field("thread", &"") .finish() } } @@ -52,6 +67,8 @@ pub fn default_ring0() -> Ring0Context { io: Arc::new(StdIo), time: Arc::new(StdTime), log: Arc::new(StdLog), + fs: Arc::new(StdFs), + thread: Arc::new(StdThread), } } diff --git a/src/runtime/ring0/std_impls.rs b/src/runtime/ring0/std_impls.rs index ab3782b3..f4b4c02c 100644 --- a/src/runtime/ring0/std_impls.rs +++ b/src/runtime/ring0/std_impls.rs @@ -2,6 +2,7 @@ use super::errors::{IoError, TimeError}; use super::traits::*; +use std::path::{Path, PathBuf}; use std::time::SystemTime; /// noop メモリ実装(Phase 88: 将来 hakmem に接続) @@ -27,21 +28,21 @@ impl IoApi for StdIo { use std::io::Write; std::io::stdout() .write_all(data) - .map_err(|e| IoError(format!("stdout write failed: {}", e))) + .map_err(|e| IoError::StdoutWriteFailed(format!("{}", e))) } fn stderr_write(&self, data: &[u8]) -> Result<(), IoError> { use std::io::Write; std::io::stderr() .write_all(data) - .map_err(|e| IoError(format!("stderr write failed: {}", e))) + .map_err(|e| IoError::StderrWriteFailed(format!("{}", e))) } fn stdin_read(&self, buf: &mut [u8]) -> Result { use std::io::Read; std::io::stdin() .read(buf) - .map_err(|e| IoError(format!("stdin read failed: {}", e))) + .map_err(|e| IoError::StdinReadFailed(format!("{}", e))) } } @@ -56,6 +57,10 @@ impl TimeApi for StdTime { fn monotonic_now(&self) -> Result { Ok(std::time::Instant::now()) } + + fn elapsed(&self, start: std::time::Instant) -> std::time::Duration { + start.elapsed() + } } /// eprintln!/println! ベースのログ実装 @@ -99,3 +104,54 @@ impl LogApi for StdLog { } } } + +/// std::fs ベースのファイルシステム実装 (Phase 90-A) +pub struct StdFs; + +impl FsApi for StdFs { + fn read_to_string(&self, path: &Path) -> Result { + std::fs::read_to_string(path).map_err(|e| { + IoError::ReadFailed(format!("read_to_string({}): {}", path.display(), e)) + }) + } + + fn read(&self, path: &Path) -> Result, IoError> { + std::fs::read(path) + .map_err(|e| IoError::ReadFailed(format!("read({}): {}", path.display(), e))) + } + + fn write_all(&self, path: &Path, data: &[u8]) -> Result<(), IoError> { + std::fs::write(path, data) + .map_err(|e| IoError::WriteFailed(format!("write({}): {}", path.display(), e))) + } + + fn exists(&self, path: &Path) -> bool { + path.exists() + } + + fn metadata(&self, path: &Path) -> Result { + let meta = std::fs::metadata(path).map_err(|e| { + IoError::MetadataFailed(format!("metadata({}): {}", path.display(), e)) + })?; + Ok(FsMetadata { + is_file: meta.is_file(), + is_dir: meta.is_dir(), + len: meta.len(), + }) + } + + fn canonicalize(&self, path: &Path) -> Result { + std::fs::canonicalize(path).map_err(|e| { + IoError::CanonicalizeFailed(format!("canonicalize({}): {}", path.display(), e)) + }) + } +} + +/// std::thread ベースのスレッド実装 (Phase 90-D) +pub struct StdThread; + +impl ThreadApi for StdThread { + fn sleep(&self, duration: std::time::Duration) { + std::thread::sleep(duration); + } +} diff --git a/src/runtime/ring0/traits.rs b/src/runtime/ring0/traits.rs index 5b832b93..d1389b67 100644 --- a/src/runtime/ring0/traits.rs +++ b/src/runtime/ring0/traits.rs @@ -4,6 +4,7 @@ //! Box 名・Nyash 型を一切知らない。 use super::errors::{IoError, TimeError}; +use std::path::{Path, PathBuf}; use std::time::SystemTime; /// メモリ API(Phase 88: noop、将来 hakmem 接続) @@ -45,6 +46,9 @@ pub trait TimeApi: Send + Sync { /// モノトニック時刻取得(高精度タイマー用) fn monotonic_now(&self) -> Result; + + /// 経過時間取得 (Phase 90-C) + fn elapsed(&self, start: std::time::Instant) -> std::time::Duration; } /// ログレベル @@ -81,3 +85,39 @@ pub trait LogApi: Send + Sync { self.log(LogLevel::Error, msg); } } + +/// ファイルシステムメタデータ +#[derive(Debug, Clone)] +pub struct FsMetadata { + pub is_file: bool, + pub is_dir: bool, + pub len: u64, +} + +/// ファイルシステム API (Phase 90-A) +pub trait FsApi: Send + Sync { + /// ファイルを文字列として読み込む + fn read_to_string(&self, path: &Path) -> Result; + + /// ファイルをバイト列として読み込む + fn read(&self, path: &Path) -> Result, IoError>; + + /// ファイルに書き込む + fn write_all(&self, path: &Path, data: &[u8]) -> Result<(), IoError>; + + /// パスが存在するか確認 + fn exists(&self, path: &Path) -> bool; + + /// ファイルメタデータを取得 + fn metadata(&self, path: &Path) -> Result; + + /// パスを正規化 + fn canonicalize(&self, path: &Path) -> Result; +} + +/// スレッド API (Phase 90-D) +pub trait ThreadApi: Send + Sync { + /// 指定時間スリープ + fn sleep(&self, duration: std::time::Duration); + // spawn は Phase 91 以降で追加予定 +}