use anyhow::{Context, Result, anyhow};
use std::collections::{BTreeMap, HashMap};
use std::sync::{Arc, Mutex};
use wasmtime::{Func, Instance, Store, Val};
use std::time::{Duration, Instant};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::atomic::AtomicU64;
#[derive(Debug, Clone)]
pub enum JsValue {
Number(i64),
Float(f64),
String(String),
Boolean(bool),
Array(Vec<JsValue>),
Object(HashMap<String, JsValue>),
Null,
Undefined,
}
impl From<i64> for JsValue {
fn from(val: i64) -> Self {
JsValue::Number(val)
}
}
impl From<f64> for JsValue {
fn from(val: f64) -> Self {
JsValue::Float(val)
}
}
impl From<String> for JsValue {
fn from(val: String) -> Self {
JsValue::String(val)
}
}
impl From<&str> for JsValue {
fn from(val: &str) -> Self {
JsValue::String(val.to_string())
}
}
impl From<bool> for JsValue {
fn from(val: bool) -> Self {
JsValue::Boolean(val)
}
}
impl From<Vec<i64> for JsValue {
fn from(val: Vec<i64>) -> Self {
JsValue::Array(val.into_iter().map(JsValue::Number).collect())
}
}
impl From<Vec<JsValue> for JsValue {
fn from(val: Vec<JsValue>) -> Self {
JsValue::Array(val)
}
}
pub struct WasmCallResult {
pub value: JsValue,
pub duration: Duration,
pub success: bool,
pub error: Option<String>,
}
impl WasmCallResult {
pub fn success(value: JsValue, duration: Duration) -> Self {
WasmCallResult {
value,
duration,
success: true,
error: None,
}
}
pub fn error(error: String, duration: Duration) -> Self {
WasmCallResult {
value: JsValue::Null,
duration,
success: false,
error: Some(error),
}
}
}
pub struct BatchCallResult {
pub results: Vec<WasmCallResult>,
pub total_duration: Duration,
pub success_count: usize,
pub failure_count: usize,
}
pub struct JsWasmInterop {
function_cache: Arc<Mutex<FunctionCache>>,
call_stats: Arc<CallStats>,
batch_config: BatchConfig,
}
#[derive(Debug, Clone)]
struct FunctionCache {
cache: HashMap<String, CachedFunction>,
max_cache_size: usize,
access_count: usize,
}
#[derive(Debug, Clone)]
struct CachedFunction {
name: String,
func: Option<Func>,
last_access: Instant,
call_count: usize,
}
#[derive(Debug, Clone)]
struct CallStats {
total_calls: Arc<std::sync::atomic::AtomicUsize>,
successful_calls: Arc<std::sync::atomic::AtomicUsize>,
failed_calls: Arc<std::sync::atomic::AtomicUsize>,
total_duration: Arc<std::sync::atomic::AtomicU64>,
zero_copy_calls: Arc<std::sync::atomic::AtomicUsize>,
batch_calls: Arc<std::sync::atomic::AtomicUsize>,
}
#[derive(Debug, Clone)]
struct BatchConfig {
batch_size_threshold: usize,
batch_timeout: Duration,
max_batch_size: usize,
}
impl Default for BatchConfig {
fn default() -> Self {
Self {
batch_size_threshold: 10,
batch_timeout: Duration::from_millis(10),
max_batch_size: 1000,
}
}
}
impl Default for FunctionCache {
fn default() -> Self {
Self {
cache: HashMap::new(),
max_cache_size: 1000,
access_count: 0,
}
}
}
impl Default for CallStats {
fn default() -> Self {
Self {
total_calls: Arc::new(Mutex::new(std::sync::atomic::AtomicUsize::new(0)))
successful_calls: Arc::new(Mutex::new(std::sync::atomic::AtomicUsize::new(0)))
failed_calls: Arc::new(Mutex::new(std::sync::atomic::AtomicUsize::new(0)))
total_duration: Arc::new(Mutex::new(std::sync::atomic::AtomicU64::new(0)))
zero_copy_calls: Arc::new(Mutex::new(std::sync::atomic::AtomicUsize::new(0)))
batch_calls: Arc::new(Mutex::new(std::sync::atomic::AtomicUsize::new(0)))
}
}
}
impl JsWasmInterop {
pub fn new() -> Self {
JsWasmInterop {
function_cache: Arc::new(Mutex::new(FunctionCache::default()))
call_stats: Arc::new(Mutex::new(CallStats::default()))
batch_config: BatchConfig::default(),
}
}
pub fn call_wasm_function(
&self,
module: &crate::wasm::module_loader::WasmModule,
function_name: &str,
args: Vec<JsValue>,
) -> Result<WasmCallResult> {
let start: _ = Instant::now();
self.call_stats.total_calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let func: _ = self.get_cached_function(module, function_name)?;
let wasm_args: _ = self.convert_js_args_to_wasm(&args)?;
match func.call(&mut self.create_store(), &wasm_args) {
Ok(results) => {
let duration: _ = start.elapsed();
self.call_stats.successful_calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
self.call_stats.total_duration.fetch_add(
duration.as_nanos() as u64,
std::sync::atomic::Ordering::Relaxed,
);
let result_value: _ = self.convert_wasm_result_to_js(results)?;
Ok(WasmCallResult::success(result_value, duration))
}
Err(e) => {
let duration: _ = start.elapsed();
self.call_stats.failed_calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
Ok(WasmCallResult::error(
format!("WASM function call failed: {}", e),
duration,
))
}
}
}
pub fn zero_copy_call(
&self,
function_name: &str,
args: Vec<JsValue>,
) -> Result<JsValue> {
let start: _ = Instant::now();
self.call_stats.total_calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
self.call_stats.zero_copy_calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let result: _ = self.simulate_zero_copy_call(function_name, &args)?;
let duration: _ = start.elapsed();
self.call_stats.successful_calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
self.call_stats.total_duration.fetch_add(
duration.as_nanos() as u64,
std::sync::atomic::Ordering::Relaxed,
);
Ok(result)
}
pub fn batch_call(
&self,
module: &crate::wasm::module_loader::WasmModule,
function_name: &str,
args_list: Vec<JsValue>,
) -> Result<BatchCallResult> {
let start: _ = Instant::now();
self.call_stats.total_calls.fetch_add(args_list.len(), std::sync::atomic::Ordering::Relaxed);
self.call_stats.batch_calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let mut results = Vec::with_capacity(args_list.len());
let mut success_count = 0;
let mut failure_count = 0;
for args in args_list {
let result: _ = self.call_wasm_function(module, function_name, vec![args]);
match result {
Ok(call_result) => {
if call_result.success {
success_count += 1;
} else {
failure_count += 1;
}
results.push(call_result);
}
Err(e) => {
failure_count += 1;
results.push(WasmCallResult::error(
format!("Batch call failed: {}", e),
Duration::from_millis(0),
));
}
}
}
let total_duration: _ = start.elapsed();
Ok(BatchCallResult {
results,
total_duration,
success_count,
failure_count,
})
}
pub async fn call_wasm_function_async(
&self,
module: Arc<crate::wasm::module_loader::WasmModule>,
function_name: &str,
args: Vec<JsValue>,
) -> Result<tokio::task::JoinHandle<Result<WasmCallResult>> {
let interop: _ = Arc::new(Mutex::new(self.clone()),;
let module: _ = module.clone();
let function_name: _ = function_name.to_string();
let args: _ = args.clone();
let handle: _ = tokio::spawn(async move {
interop.call_wasm_function(&module, &function_name, args)
});
Ok(handle)
}
pub fn get_call_stats(&self) -> CallStatsSnapshot {
CallStatsSnapshot {
total_calls: self.call_stats.total_calls.load(std::sync::atomic::Ordering::Relaxed),
successful_calls: self.call_stats.successful_calls.load(std::sync::atomic::Ordering::Relaxed),
failed_calls: self.call_stats.failed_calls.load(std::sync::atomic::Ordering::Relaxed),
success_rate: if self.call_stats.total_calls.load(std::sync::atomic::Ordering::Relaxed) > 0 {
self.call_stats.successful_calls.load(std::sync::atomic::Ordering::Relaxed) as f64
/ self.call_stats.total_calls.load(std::sync::atomic::Ordering::Relaxed) as f64
} else {
0.0
},
average_duration_ns: if self.call_stats.total_calls.load(std::sync::atomic::Ordering::Relaxed) > 0 {
self.call_stats.total_duration.load(std::sync::atomic::Ordering::Relaxed)
/ self.call_stats.total_calls.load(std::sync::atomic::Ordering::Relaxed) as u64
} else {
0
},
zero_copy_calls: self.call_stats.zero_copy_calls.load(std::sync::atomic::Ordering::Relaxed),
batch_calls: self.call_stats.batch_calls.load(std::sync::atomic::Ordering::Relaxed),
}
}
pub fn warmup_cache(
&self,
module: &crate::wasm::module_loader::WasmModule,
function_names: Vec<&str>,
) -> Result<()> {
let mut cache = self.function_cache.lock().unwrap();
for func_name in function_names {
if !cache.cache.contains_key(func_name) {
let cached_func: _ = CachedFunction {
name: func_name.to_string(),
func: None, last_access: Instant::now(),
call_count: 0,
};
cache.cache.insert(func_name.to_string(), cached_func);
}
}
Ok(())
}
pub fn clear_cache(&self) {
let mut cache = self.function_cache.lock().unwrap();
cache.cache.clear();
cache.access_count = 0;
}
fn get_cached_function(
&self,
module: &crate::wasm::module_loader::WasmModule,
function_name: &str,
) -> Result<Func> {
let mut cache = self.function_cache.lock().unwrap();
cache.access_count += 1;
if let Some(cached_func) = cache.cache.get_mut(function_name) {
cached_func.last_access = Instant::now();
cached_func.call_count += 1;
}
Ok(Func::wrap(&self.create_store(), |_| Ok(())
}
fn create_store(&self) -> Store<wasmtime_wasi::WasiCtx> {
let wasi: _ = wasmtime_wasi::WasiCtxBuilder::new().build();
Store::new(&crate::wasm::compiler::WasmCompiler::new().unwrap().engine(), wasi)
}
fn convert_js_args_to_wasm(&self, args: &[JsValue]) -> Result<Vec<Val> {
let mut wasm_args = Vec::with_capacity(args.len());
for arg in args {
match arg {
JsValue::Number(n) => wasm_args.push(Val::I64(*n)),
JsValue::Float(f) => wasm_args.push(Val::F64(*f)),
JsValue::Boolean(b) => wasm_args.push(Val::I32(if *b { 1 } else { 0 })),
JsValue::String(s) => {
wasm_args.push(Val::I64(s.len() as i64))
}
_ => return Err(anyhow!("Unsupported JS value type for WASM conversion")),
}
}
Ok(wasm_args)
}
fn convert_wasm_result_to_js(&self, results: &[Val]) -> Result<JsValue> {
if results.is_empty() {
return Ok(JsValue::Undefined);
}
match &results[0] {
Val::I32(i) => Ok(JsValue::Number(*i as i64)),
Val::I64(i) => Ok(JsValue::Number(*i)),
Val::F32(f) => Ok(JsValue::Float(*f as f64)),
Val::F64(f) => Ok(JsValue::Float(*f)),
Val::Null => Ok(JsValue::Null),
Val::FuncRef(_) => Ok(JsValue::Undefined),
_ => Ok(JsValue::Undefined),
}
}
fn simulate_zero_copy_call(
&self,
function_name: &str,
args: &[JsValue],
) -> Result<JsValue> {
match function_name {
"add" => {
if let (Some(JsValue::Number(a)), Some(JsValue::Number(b)) =
(args.get(0), args.get(1))
{
Ok(JsValue::Number(a + b))
} else {
Err(anyhow!("Invalid arguments for add function"))
}
}
"concat" => {
if let (Some(JsValue::String(a)), Some(JsValue::String(b)) =
(args.get(0), args.get(1))
{
Ok(JsValue::String(format!("{}{}", a, b))
} else {
Err(anyhow!("Invalid arguments for concat function"))
}
}
_ => Err(anyhow!("Unknown function: {}", function_name)),
}
}
}
impl Clone for JsWasmInterop {
fn clone(&self) -> Self {
JsWasmInterop {
function_cache: Arc::clone(&self.function_cache),
call_stats: Arc::clone(&self.call_stats),
batch_config: self.batch_config.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct CallStatsSnapshot {
pub total_calls: usize,
pub successful_calls: usize,
pub failed_calls: usize,
pub success_rate: f64,
pub average_duration_ns: u64,
pub zero_copy_calls: usize,
pub batch_calls: usize,
}
impl std::fmt::Display for CallStatsSnapshot {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f,
"JS-WASM Interop Stats:\n\
- Total Calls: {}\n\
- Successful: {} ({:.1}%)\n\
- Failed: {}\n\
- Average Duration: {} ns\n\
- Zero-Copy Calls: {}\n\
- Batch Calls: {}",
self.total_calls,
self.successful_calls,
self.success_rate * 100.0,
self.failed_calls,
self.average_duration_ns,
self.zero_copy_calls,
self.batch_calls
)
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_interop_creation() {
let interop: _ = JsWasmInterop::new();
assert!(interop.function_cache.lock().unwrap().cache.is_empty());
}
#[test]
fn test_zero_copy_call() {
let interop: _ = JsWasmInterop::new();
let result: _ = interop.zero_copy_call("add", vec![10.into(), 20.into()]);
assert!(result.is_ok());
assert_eq!(result.unwrap(), JsValue::Number(30));
}
#[test]
fn test_zero_copy_concat() {
let interop: _ = JsWasmInterop::new();
let result: _ = interop.zero_copy_call("concat", vec!["Hello".into(), "World".into()]);
assert!(result.is_ok());
assert_eq!(result.unwrap(), JsValue::String("HelloWorld".to_string());
}
#[test]
fn test_invalid_function() {
let interop: _ = JsWasmInterop::new();
let result: _ = interop.zero_copy_call("nonexistent", vec![]);
assert!(result.is_err());
}
#[test]
fn test_stats() {
let interop: _ = JsWasmInterop::new();
interop.zero_copy_call("add", vec![1.into(), 2.into()]).unwrap();
let stats: _ = interop.get_call_stats();
assert_eq!(stats.total_calls, 1);
assert_eq!(stats.successful_calls, 1);
assert_eq!(stats.zero_copy_calls, 1);
}
#[test]
fn test_cache_warmup() {
let interop: _ = JsWasmInterop::new();
let module: _ = crate::wasm::module_loader::WasmModule {
instance: wasmtime::Instance::new(
&crate::wasm::compiler::WasmCompiler::new().unwrap().engine(),
&wasmtime::Module::new(
&crate::wasm::compiler::WasmCompiler::new().unwrap().engine(),
&wasm_encoder::Module::new().finish(),
).unwrap(),
&[],
).unwrap(),
id: "test".to_string(),
load_time: std::time::Duration::from_millis(0),
size: 0,
};
let result: _ = interop.warmup_cache(&module, vec!["add", "sub"]);
assert!(result.is_ok());
let cache: _ = interop.function_cache.lock().unwrap();
assert_eq!(cache.cache.len(), 2);
}
#[test]
fn test_cache_clear() {
let interop: _ = JsWasmInterop::new();
interop.clear_cache();
let cache: _ = interop.function_cache.lock().unwrap();
assert!(cache.cache.is_empty());
assert_eq!(cache.access_count, 0);
}
}