use anyhow::{Context, Result, anyhow};
use crate::code_cache::{BytecodeCache, CacheConfig};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use std::time::SystemTime;
use std::path::PathBuf;
use std::hash::{Hash, Hasher, DefaultHasher};
#[derive(Debug, Clone, Default)]
pub struct PrecompiledCacheStats {
pub total_modules: usize,
pub cached_modules: usize,
pub cache_hits: u64,
pub cache_misses: u64,
pub average_compile_time_ms: f64,
pub total_compile_time_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrecompiledModuleEntry {
pub module_name: String,
pub bytecode: Vec<u8>,
pub source_hash: String,
pub compile_time_ms: u64,
pub created_at: std::time::SystemTime,
}
pub struct PrecompiledModuleCache {
cache_dir: PathBuf,
modules: Arc<Mutex<HashMap<String, PrecompiledModuleEntry>>>,
stats: Arc<Mutex<PrecompiledCacheStats>>,
#[allow(dead_code)]
bytecode_cache: Arc<BytecodeCache>,
}
impl PrecompiledModuleCache {
pub fn new() -> Result<Self> {
let cache_dir: _ = std::env::temp_dir().join("amberjs_precompiled_cache");
Self::new_with_path(cache_dir)
}
pub fn new_with_path(cache_dir: PathBuf) -> Result<Self> {
fs::create_dir_all(&cache_dir)
.context(format!("Failed to create cache directory: {:?}", cache_dir))?;
let cache_config: _ = CacheConfig::default();
Ok(Self {
cache_dir,
modules: Arc::new(Mutex::new(HashMap::new()))
stats: Arc::new(Mutex::new(PrecompiledCacheStats::default()))
bytecode_cache: Arc::new(Mutex::new(BytecodeCache::new(cache_config)))
})
}
pub fn load_from_path(cache_dir: PathBuf) -> Result<Self> {
let mut cache = PrecompiledModuleCache::new_with_path(cache_dir)?;
cache.load_from_disk()?;
Ok(cache)
}
pub fn precompile_builtin_modules(&self) -> Result<()> {
let builtin_modules: _ = Self::get_builtin_modules_list();
for module in &builtin_modules {
if !self.is_module_cached(module) {
let source: _ = self.get_builtin_module_source(module)?;
self.cache_module(module, &source)?;
}
}
Ok(())
}
pub fn get_builtin_modules_list() -> Vec<&'static str> {
vec![
"console",
"process",
"path",
"fs",
"os",
"util",
"events",
"stream",
"buffer",
"crypto",
]
}
fn get_builtin_module_source(&self, module_name: &str) -> Result<String> {
match module_name {
"console" => Ok(Self::builtin_console_module()),
"process" => Ok(Self::builtin_process_module()),
"path" => Ok(Self::builtin_path_module()),
"fs" => Ok(Self::builtin_fs_module()),
"os" => Ok(Self::builtin_os_module()),
"util" => Ok(Self::builtin_util_module()),
"events" => Ok(Self::builtin_events_module()),
"stream" => Ok(Self::builtin_stream_module()),
"buffer" => Ok(Self::builtin_buffer_module()),
"crypto" => Ok(Self::builtin_crypto_module()),
_ => Err(anyhow!("Unknown builtin module: {}", module_name)),
}
}
pub fn cache_module(&self, module_name: &str, source_code: &str) -> Result<()> {
let start_time: _ = Instant::now();
let source_hash: _ = self.calculate_source_hash(source_code);
let bytecode: _ = self.compile_to_bytecode(source_code)?;
let compile_time: _ = start_time.elapsed();
let entry: _ = PrecompiledModuleEntry {
module_name: module_name.to_string(),
bytecode,
source_hash,
compile_time_ms: compile_time.as_millis() as u64,
created_at: std::time::SystemTime::now(),
};
{
let mut modules = self.modules.lock().unwrap();
modules.insert(module_name.to_string(), entry.clone());
}
self.persist_module(module_name, &entry)?;
self.update_stats(compile_time);
Ok(())
}
pub fn is_module_cached(&self, module_name: &str) -> bool {
let modules: _ = self.modules.lock().unwrap();
modules.contains_key(module_name)
}
pub fn get_precompiled_bytecode(&self, module_name: &str) -> Option<Vec<u8> {
let modules: _ = self.modules.lock().unwrap();
if let Some(entry) = modules.get(module_name) {
{
let mut stats = self.stats.lock().unwrap();
stats.cache_hits += 1;
}
Some(entry.bytecode.clone())
} else {
{
let mut stats = self.stats.lock().unwrap();
stats.cache_misses += 1;
}
None
}
}
pub fn invalidate_module(&self, module_name: &str) -> Result<()> {
{
let mut modules = self.modules.lock().unwrap();
modules.remove(module_name);
}
let module_file: _ = self.cache_dir.join(format!("{}.bin", module_name));
if module_file.exists() {
fs::remove_file(&module_file)
.context(format!("Failed to remove cached module: {}", module_name))?;
}
Ok(())
}
pub fn get_stats(&self) -> PrecompiledCacheStats {
let modules: _ = self.modules.lock().unwrap();
let stats: _ = self.stats.lock().unwrap();
PrecompiledCacheStats {
total_modules: modules.len(),
cached_modules: modules.len(),
cache_hits: stats.cache_hits,
cache_misses: stats.cache_misses,
average_compile_time_ms: if stats.cache_hits + stats.cache_misses > 0 {
stats.total_compile_time_ms as f64 / (stats.cache_hits + stats.cache_misses) as f64
} else {
0.0
},
total_compile_time_ms: stats.total_compile_time_ms,
}
}
fn calculate_source_hash(&self, source: &str) -> String {
let mut hasher = DefaultHasher::new();
source.hash(&mut hasher);
format!("{:x}", hasher.finish())
}
fn compile_to_bytecode(&self, source: &str) -> Result<Vec<u8> {
let bytecode: _ = source.as_bytes().to_vec();
Ok(bytecode)
}
fn update_stats(&self, compile_time: Duration) {
let mut stats = self.stats.lock().unwrap();
stats.total_compile_time_ms += compile_time.as_millis() as u64;
}
fn persist_module(&self, module_name: &str, entry: &PrecompiledModuleEntry) -> Result<()> {
let module_file: _ = self.cache_dir.join(format!("{}.bin", module_name));
let serialized: _ = bincode::serialize(entry)
.context(format!("Failed to serialize module: {}", module_name))?;
fs::write(&module_file, serialized)
.context(format!("Failed to write cached module: {:?}", module_file))?;
Ok(())
}
fn load_from_disk(&mut self) -> Result<()> {
if !self.cache_dir.exists() {
return Ok(());
}
let entries: _ = fs::read_dir(&self.cache_dir)
.context(format!("Failed to read cache directory: {:?}", self.cache_dir))?;
for entry in entries {
let entry: _ = entry?;
let path: _ = entry.path();
if path.extension().map_or(false, |ext| ext == "bin") {
let serialized: _ = fs::read(&path)
.context(format!("Failed to read cached file: {:?}", path))?;
let module_entry: PrecompiledModuleEntry = bincode::deserialize(&serialized)
.context(format!("Failed to deserialize module: {:?}", path))?;
let module_name: _ = module_entry.module_name.clone();
let mut modules = self.modules.lock().unwrap();
modules.insert(module_name, module_entry);
}
}
Ok(())
}
fn builtin_console_module() -> String {
r#"
const console = {
log: (...args) => {
// 将日志输出到 V8
},
error: (...args) => {
// 将错误输出到 stderr
},
warn: (...args) => {
// 将警告输出到 stderr
},
info: (...args) => {
// 将信息输出到 stdout
},
debug: (...args) => {
// 调试输出
}
};
module.exports = console;
"#
.to_string()
}
fn builtin_process_module() -> String {
r#"
const process = {
version: 'v18.0.0',
argv: [],
env: {},
cwd: () => '/',
nextTick: (fn) => {
// 异步执行
setTimeout(fn, 0);
}
};
module.exports = process;
"#
.to_string()
}
fn builtin_path_module() -> String {
r#"
const path = {
join: (...paths) => {
return paths.join('/');
},
resolve: (...paths) => {
return '/' + paths.join('/');
},
dirname: (p) => {
return p.substring(0, p.lastIndexOf('/'));
},
basename: (p) => {
const parts = p.split('/');
return parts[parts.length - 1];
}
};
module.exports = path;
"#
.to_string()
}
fn builtin_fs_module() -> String {
r#"
const fs = {
readFile: (path, callback) => {
// 异步文件读取
},
writeFile: (path, data, callback) => {
// 异步文件写入
}
};
module.exports = fs;
"#
.to_string()
}
fn builtin_os_module() -> String {
r#"
const os = {
platform: () => process.platform,
arch: () => process.arch,
cpus: () => []
};
module.exports = os;
"#
.to_string()
}
fn builtin_util_module() -> String {
r#"
const util = {
format: (format, ...args) => {
return format.replace(/%[sdif]/g, (match) => {
return args.shift() || match;
});
},
inspect: (obj) => {
return JSON.stringify(obj);
}
};
module.exports = util;
"#
.to_string()
}
fn builtin_events_module() -> String {
r#"
class EventEmitter {
constructor() {
this.events = {};
}
on(event, listener) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
}
emit(event, ...args) {
if (this.events[event]) {
this.events[event].forEach(fn => fn(...args));
}
}
}
module.exports = EventEmitter;
"#
.to_string()
}
fn builtin_stream_module() -> String {
r#"
const stream = {
Readable: class {
constructor() {}
on(event, listener) {}
read() {}
},
Writable: class {
constructor() {}
on(event, listener) {}
write(data) {}
}
};
module.exports = stream;
"#
.to_string()
}
fn builtin_buffer_module() -> String {
r#"
class Buffer {
constructor(data) {
this.data = data;
}
toString() {
return String(this.data);
}
}
module.exports = Buffer;
"#
.to_string()
}
fn builtin_crypto_module() -> String {
r#"
const crypto = {
createHash: (algorithm) => {
return {
update: (data) => this,
digest: (encoding) => ''
};
}
};
module.exports = crypto;
"#
.to_string()
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_cache_creation() {
let cache: _ = PrecompiledModuleCache::new();
assert!(cache.is_ok());
}
#[test]
fn test_builtin_modules_list() {
let modules: _ = PrecompiledModuleCache::get_builtin_modules_list();
assert!(modules.len() > 0);
assert!(modules.contains(&"console"));
assert!(modules.contains(&"process"));
}
#[test]
fn test_module_caching() {
let cache: _ = PrecompiledModuleCache::new().unwrap();
let source: _ = "module.exports = { test: true };";
let result: _ = cache.cache_module("test_module", source);
assert!(result.is_ok());
assert!(cache.is_module_cached("test_module"));
}
#[test]
fn test_cache_stats() {
let cache: _ = PrecompiledModuleCache::new().unwrap();
cache.precompile_builtin_modules().unwrap();
let stats: _ = cache.get_stats();
assert!(stats.cached_modules > 0);
}
}