Files
hakorune/src/jit/manager.rs
Moe Charm c13d9c045e 📚 Phase 12: Nyashスクリプトプラグインシステム設計と埋め込みVM構想
## 主な成果
- Nyashスクリプトでプラグイン作成可能という革命的発見
- C ABI制約の分析と埋め込みVMによる解決策
- MIR/VM/JIT層での箱引数サポートの詳細分析

## ドキュメント作成
- Phase 12基本構想(README.md)
- Gemini/Codex先生の技術分析
- C ABIとの整合性問題と解決策
- 埋め込みVM実装ロードマップ
- 箱引数サポートの技術詳細

## 重要な洞察
- 制約は「リンク時にC ABI必要」のみ
- 埋め込みVMでMIRバイトコード実行により解決可能
- Nyashスクリプト→C ABIプラグイン変換が実現可能

Everything is Box → Everything is Plugin → Everything is Possible!
2025-08-30 22:52:16 +09:00

194 lines
9.1 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 std::collections::HashMap;
/// Minimal JIT manager skeleton for Phase 10_a
/// - Tracks per-function entry counts
/// - Decides when a function should be JIT-compiled (threshold)
/// - Records compiled functions for stats
pub struct JitManager {
threshold: u32,
hits: HashMap<String, u32>,
compiled: HashMap<String, u64>,
engine: crate::jit::engine::JitEngine,
exec_ok: u64,
exec_trap: u64,
// Per-function lowering stats (accumulated)
func_phi_total: HashMap<String, u64>,
func_phi_b1: HashMap<String, u64>,
func_ret_bool_hint: HashMap<String, u64>,
}
impl JitManager {
pub fn new(threshold: u32) -> Self {
Self { threshold, hits: HashMap::new(), compiled: HashMap::new(), engine: crate::jit::engine::JitEngine::new(), exec_ok: 0, exec_trap: 0, func_phi_total: HashMap::new(), func_phi_b1: HashMap::new(), func_ret_bool_hint: HashMap::new() }
}
pub fn set_threshold(&mut self, t: u32) { self.threshold = t.max(1); }
pub fn record_entry(&mut self, func: &str) {
let c = self.hits.entry(func.to_string()).or_insert(0);
*c = c.saturating_add(1);
}
pub fn should_jit(&self, func: &str) -> bool {
let hot = self.hits.get(func).copied().unwrap_or(0) >= self.threshold;
hot && !self.compiled.contains_key(func)
}
pub fn mark_compiled(&mut self, func: &str, handle: u64) {
self.compiled.insert(func.to_string(), handle);
}
/// Ensure the function is compiled when hot; returns true if compiled now or already compiled
pub fn maybe_compile(&mut self, func: &str, mir: &crate::mir::MirFunction) -> bool {
if self.should_jit(func) {
if let Some(handle) = self.engine.compile_function(func, mir) {
self.mark_compiled(func, handle);
// Record per-function lower stats captured by engine
let (phi_t, phi_b1, ret_b) = self.engine.last_lower_stats();
self.record_lower_stats(func, phi_t, phi_b1, ret_b);
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
eprintln!("[JIT] compiled {} -> handle={}", func, handle);
}
return true;
}
}
self.compiled.contains_key(func)
}
pub fn is_compiled(&self, func: &str) -> bool { self.compiled.contains_key(func) }
pub fn handle_of(&self, func: &str) -> Option<u64> { self.compiled.get(func).copied() }
// --- Stats accessors for unified reporting ---
pub fn sites(&self) -> usize { self.hits.len() }
pub fn compiled_count(&self) -> usize { self.compiled.len() }
pub fn total_hits(&self) -> u64 { self.hits.values().map(|v| *v as u64).sum() }
pub fn exec_ok_count(&self) -> u64 { self.exec_ok }
pub fn exec_trap_count(&self) -> u64 { self.exec_trap }
// --- Per-function stats ---
pub fn record_lower_stats(&mut self, func: &str, phi_total: u64, phi_b1: u64, ret_bool_hint: bool) {
if phi_total > 0 { *self.func_phi_total.entry(func.to_string()).or_insert(0) += phi_total; }
if phi_b1 > 0 { *self.func_phi_b1.entry(func.to_string()).or_insert(0) += phi_b1; }
if ret_bool_hint { *self.func_ret_bool_hint.entry(func.to_string()).or_insert(0) += 1; }
}
pub fn per_function_stats(&self) -> Vec<(String, u64, u64, u64, u32, bool, u64)> {
// name, phi_total, phi_b1, ret_bool_hint, hits, compiled, handle
let mut names: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
names.extend(self.hits.keys().cloned());
names.extend(self.func_phi_total.keys().cloned());
names.extend(self.func_phi_b1.keys().cloned());
names.extend(self.func_ret_bool_hint.keys().cloned());
let mut out = Vec::new();
for name in names {
let phi_t = self.func_phi_total.get(&name).copied().unwrap_or(0);
let phi_b1 = self.func_phi_b1.get(&name).copied().unwrap_or(0);
let rb = self.func_ret_bool_hint.get(&name).copied().unwrap_or(0);
let hits = self.hits.get(&name).copied().unwrap_or(0);
let compiled = self.compiled.contains_key(&name);
let handle = self.compiled.get(&name).copied().unwrap_or(0);
out.push((name, phi_t, phi_b1, rb, hits, compiled, handle));
}
out
}
/// Return top-N hot functions by hits, with compiled flag and handle
pub fn top_hits(&self, n: usize) -> Vec<(String, u32, bool, u64)> {
let mut v: Vec<(&String, &u32)> = self.hits.iter().collect();
v.sort_by(|a, b| b.1.cmp(a.1));
v.into_iter()
.take(n)
.map(|(k, h)| {
let compiled = self.compiled.contains_key(k);
let handle = self.compiled.get(k).copied().unwrap_or(0);
(k.clone(), *h, compiled, handle)
})
.collect()
}
pub fn print_summary(&self) {
if std::env::var("NYASH_JIT_STATS").ok().as_deref() != Some("1") { return; }
let sites = self.hits.len();
let total_hits: u64 = self.hits.values().map(|v| *v as u64).sum();
let compiled = self.compiled.len();
eprintln!("[JIT] sites={} compiled={} hits_total={} exec_ok={} exec_trap={}", sites, compiled, total_hits, self.exec_ok, self.exec_trap);
// Top 5 hot functions
let mut v: Vec<(&String, &u32)> = self.hits.iter().collect();
v.sort_by(|a, b| b.1.cmp(a.1));
for (i, (k, h)) in v.into_iter().take(5).enumerate() {
let comp = if self.compiled.contains_key(k) { "*" } else { " " };
let hdl = self.compiled.get(k).copied().unwrap_or(0);
eprintln!(" #{}{} {} hits={} handle={}", i+1, comp, k, h, hdl);
}
}
/// Phase 10_c stub: attempt to dispatch to JIT if enabled; returns true if it would execute
pub fn maybe_dispatch(&mut self, func: &str, argc: usize) -> bool {
if std::env::var("NYASH_JIT_EXEC").ok().as_deref() == Some("1") {
if let Some(h) = self.handle_of(func) {
eprintln!("[JIT] executing handle={} argc={} (stub) for {}", h, argc, func);
// In 10_c proper, invoke engine with prepared args and return actual result
// For now, execute with empty args to exercise the path, ignore result
let _ = self.engine.execute_handle(h, &[]);
return false; // keep VM path active for now
}
}
false
}
/// 10_c: execute compiled function if present (stub: empty args). Returns Some(VMValue) if JIT path was taken.
pub fn execute_compiled(&mut self, func: &str, ret_ty: &crate::mir::MirType, args: &[crate::backend::vm::VMValue]) -> Option<crate::backend::vm::VMValue> {
// Strict/FailFastモードではJITは"コンパイル専用"(実行しない)
if std::env::var("NYASH_JIT_STRICT").ok().as_deref() == Some("1") {
// 観測のためイベントだけ出す
crate::jit::events::emit_runtime(
serde_json::json!({
"id": "jit_skip_execute_strict",
"func": func
}),
"jit", func
);
return None;
}
if let Some(h) = self.handle_of(func) {
// Expose args to both legacy VM hostcalls and new JIT ABI TLS
crate::jit::rt::set_legacy_vm_args(args);
let jit_args = crate::jit::abi::adapter::to_jit_values(args);
crate::jit::rt::set_current_jit_args(&jit_args);
let t0 = std::time::Instant::now();
// Begin handle scope so temporary handles are reclaimed after the call
crate::jit::rt::handles::begin_scope();
let out = self.engine.execute_handle(h, &jit_args);
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
let dt = t0.elapsed();
eprintln!("[JIT] exec_time_ms={} for {}", dt.as_millis(), func);
}
let res = match out {
Some(v) => {
self.exec_ok = self.exec_ok.saturating_add(1);
// Use CallBoundaryBox to convert JitValue → VMValue with MIR ret type hint
let vmv = crate::jit::boundary::CallBoundaryBox::to_vm(ret_ty, v);
Some(vmv)
}
None => {
self.exec_trap = self.exec_trap.saturating_add(1);
// Emit a minimal trap event for observability (runtime only)
let dt = t0.elapsed();
crate::jit::events::emit_runtime(
serde_json::json!({
"kind": "trap", // redundant with wrapper kind but explicit here for clarity
"reason": "jit_execute_failed",
"ms": dt.as_millis()
}),
"trap", func
);
None
}
};
// Clear handles created during this call
crate::jit::rt::handles::end_scope_clear();
return res;
}
None
}
}