use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::any::Any;
use dashmap::DashMap;
use wasmtime::{Engine, Module, Store, Linker, Config, Val, Memory, Caller, AsContextMut, Extern, Func};
use wasmtime_wasi::{WasiCtx, WasiCtxBuilder};
use crate::error::{Error, Result};
use crate::runtime::{
ModuleId, RuntimeConfig, RuntimeMetrics,
WasmInstance, WasmInstanceState, WasmModule, WasmRuntime
};
use crate::security::{Capabilities, ResourceLimits};
pub struct WasmtimeModule {
id: ModuleId,
name: Option<String>,
module: Module,
exports: Vec<String>,
size: usize,
}
impl WasmtimeModule {
pub fn new(module: Module, wasm_bytes: &[u8]) -> Self {
let exports = module.exports()
.map(|export| export.name().to_string())
.collect();
let name = None;
Self {
id: ModuleId::new(),
name,
module,
exports,
size: wasm_bytes.len(),
}
}
pub fn module(&self) -> &Module {
&self.module
}
}
impl WasmModule for WasmtimeModule {
fn id(&self) -> ModuleId {
self.id
}
fn name(&self) -> Option<&str> {
self.name.as_deref()
}
fn size(&self) -> usize {
self.size
}
fn exports(&self) -> Vec<String> {
self.exports.clone()
}
fn clone_module(&self) -> Box<dyn WasmModule> {
Box::new(Self {
id: self.id,
name: self.name.clone(),
module: self.module.clone(),
exports: self.exports.clone(),
size: self.size,
})
}
}
pub struct WasmtimeStoreData {
wasi_ctx: WasiCtx,
state: WasmInstanceState,
}
pub struct WasmtimeInstance {
store: Store<WasmtimeStoreData>,
linker: Arc<Linker<WasmtimeStoreData>>,
module_id: ModuleId,
}
impl WasmtimeInstance {
pub fn new(
store: Store<WasmtimeStoreData>,
linker: Arc<Linker<WasmtimeStoreData>>,
module_id: ModuleId,
) -> Self {
Self {
store,
linker,
module_id,
}
}
}
impl WasmInstance for WasmtimeInstance {
fn state(&self) -> WasmInstanceState {
self.store.data().state
}
async fn call_function<P, R>(
&self,
function_name: &str,
params: &P,
) -> Result<R>
where
P: serde::Serialize + ?Sized,
R: for<'de> serde::Deserialize<'de>,
{
let params_json = serde_json::to_string(params)
.map_err(|e| Error::FunctionCall {
function_name: function_name.to_string(),
reason: format!("Failed to serialize parameters: {}", e),
})?;
let func = self.linker.get(&mut self.store, "", function_name)
.ok_or_else(|| Error::FunctionCall {
function_name: function_name.to_string(),
reason: "Function not found in instance".to_string(),
})?;
let result = func.call_async(&mut self.store, [wasmtime::Val::String(self.store.as_context_mut(), params_json.into())])
.await
.map_err(|e| Error::FunctionCall {
function_name: function_name.to_string(),
reason: format!("Function call failed: {}", e),
})?;
let result_string = if let Some(val) = result.first() {
if let wasmtime::Val::String(s) = val {
s.to_string().map_err(|e| Error::FunctionCall {
function_name: function_name.to_string(),
reason: format!("Failed to convert result to string: {}", e),
})?
} else {
return Err(Error::FunctionCall {
function_name: function_name.to_string(),
reason: "Function returned a non-string value".to_string(),
});
}
} else {
return Err(Error::FunctionCall {
function_name: function_name.to_string(),
reason: "Function returned no values".to_string(),
});
};
serde_json::from_str(&result_string)
.map_err(|e| Error::FunctionCall {
function_name: function_name.to_string(),
reason: format!("Failed to deserialize result: {}", e),
})
}
fn memory_usage(&self) -> usize {
if let Ok(memory) = self.get_memory() {
let data_size = memory.data_size(&self.store);
return data_size;
}
0
}
fn fuel_usage(&self) -> Option<u64> {
if let Ok(fuel_consumed) = wasmtime::Fuel::get_consumed(&mut self.store.as_context_mut()) {
return Some(fuel_consumed);
}
None
}
fn reset_fuel(&self) -> Result<()> {
if let Err(e) = wasmtime::Fuel::reset(&mut self.store.as_context_mut(), 0) {
return Err(Error::ResourceLimit(format!("Failed to reset fuel: {}", e)));
}
Ok(())
}
fn add_fuel(&self, fuel: u64) -> Result<()> {
if let Err(e) = wasmtime::Fuel::add(&mut self.store.as_context_mut(), fuel) {
return Err(Error::ResourceLimit(format!("Failed to add fuel: {}", e)));
}
Ok(())
}
unsafe fn memory_ptr(&self) -> Result<*mut u8> {
if let Ok(memory) = self.get_memory() {
let ptr = memory.data_ptr(&self.store);
return Ok(ptr);
}
Err(Error::UnsupportedOperation("No memory exported by the module".to_string()))
}
fn memory_size(&self) -> usize {
self.memory_usage()
}
fn get_memory(&self) -> Result<wasmtime::Memory> {
let memory = self.linker
.get(&mut self.store, "", "memory")
.and_then(|ext| ext.into_memory())
.ok_or_else(|| {
Error::UnsupportedOperation("No memory exported by the module".to_string())
})?;
Ok(memory)
}
}
pub struct WasmtimeRuntime {
engine: Engine,
config: RuntimeConfig,
modules: DashMap<ModuleId, Arc<WasmtimeModule>>,
metrics: Mutex<RuntimeMetrics>,
}
impl WasmtimeRuntime {
pub fn new(config: &RuntimeConfig) -> Result<Self> {
let mut wasmtime_config = Config::new();
if config.enable_fuel {
wasmtime_config.consume_fuel(true);
}
if config.enable_memory_limits {
wasmtime_config.memory_init_cow(true);
}
if config.native_stack_trace {
wasmtime_config.native_stack_trace(true);
}
if config.debug_info {
wasmtime_config.debug_info(true);
}
wasmtime_config.cranelift_set_compilation_threads(config.compilation_threads);
if config.cache_modules {
if let Some(cache_dir) = &config.cache_directory {
wasmtime_config.cache_config_load(&cache_dir.to_string_lossy())
.map_err(|e| Error::RuntimeInitialization(
format!("Failed to configure cache: {}", e)
))?;
} else {
wasmtime_config.cache_config_load_default()
.map_err(|e| Error::RuntimeInitialization(
format!("Failed to configure default cache: {}", e)
))?;
}
}
let engine = Engine::new(&wasmtime_config)
.map_err(|e| Error::RuntimeInitialization(
format!("Failed to create Wasmtime engine: {}", e)
))?;
Ok(Self {
engine,
config: config.clone(),
modules: DashMap::new(),
metrics: Mutex::new(RuntimeMetrics {
compiled_modules: 0,
active_instances: 0,
total_memory_usage: 0,
peak_memory_usage: 0,
fuel_consumption_rate: None,
cache_hit_rate: None,
last_compilation_time_ms: None,
}),
})
}
}
impl WasmRuntime for WasmtimeRuntime {
fn initialize(&mut self, config: RuntimeConfig) -> Result<()> {
self.config = config;
Ok(())
}
fn load_module(&self, wasm_bytes: &[u8]) -> Result<Box<dyn WasmModule>> {
let start_time = std::time::Instant::now();
let module = Module::new(&self.engine, wasm_bytes)
.map_err(|e| Error::ModuleLoad(format!("Failed to compile module: {}", e)))?;
let elapsed_ms = start_time.elapsed().as_millis() as u64;
{
let mut metrics = self.metrics.lock().unwrap();
metrics.compiled_modules += 1;
metrics.last_compilation_time_ms = Some(elapsed_ms);
}
let module = WasmtimeModule::new(module, wasm_bytes);
let id = module.id();
let module_arc = Arc::new(module);
self.modules.insert(id, module_arc.clone());
Ok(Box::new(WasmtimeModule {
id,
name: module_arc.name.clone(),
module: module_arc.module.clone(),
exports: module_arc.exports.clone(),
size: module_arc.size,
}))
}
fn get_module(&self, id: ModuleId) -> Result<Arc<dyn WasmModule>> {
let module = self.modules.get(&id)
.ok_or_else(|| Error::ModuleNotFound(format!("Module not found: {}", id)))?;
let module_clone: Arc<dyn WasmModule> = module.clone();
Ok(module_clone)
}
fn create_instance(
&self,
module: &dyn WasmModule,
resources: ResourceLimits,
capabilities: Capabilities,
) -> Result<Box<dyn WasmInstance>> {
let wasmtime_module = match module.clone_module().downcast::<WasmtimeModule>() {
Ok(m) => m,
Err(_) => return Err(Error::InstanceCreation(
"Module is not a Wasmtime module".to_string()
)),
};
let mut wasi_builder = WasiCtxBuilder::new();
match &capabilities.environment {
crate::security::EnvironmentCapability::None => {
},
crate::security::EnvironmentCapability::Allowlist(vars) => {
let env_vars = std::env::vars()
.filter(|(k, _)| vars.contains(k))
.collect::<HashMap<String, String>>();
for (k, v) in env_vars {
wasi_builder.env(&k, &v);
}
},
crate::security::EnvironmentCapability::Denylist(vars) => {
let env_vars = std::env::vars()
.filter(|(k, _)| !vars.contains(k))
.collect::<HashMap<String, String>>();
for (k, v) in env_vars {
wasi_builder.env(&k, &v);
}
},
crate::security::EnvironmentCapability::Full => {
for (k, v) in std::env::vars() {
wasi_builder.env(&k, &v);
}
},
}
for dir in &capabilities.filesystem.readable_dirs {
wasi_builder.preopened_dir(dir, "/").map_err(|e| Error::InstanceCreation(
format!("Failed to preopen directory: {}", e)
))?;
}
let wasi_ctx = wasi_builder.build();
let mut store = Store::new(
&self.engine,
WasmtimeStoreData {
wasi_ctx,
state: WasmInstanceState::Created,
}
);
if self.config.enable_fuel {
if let Some(fuel) = resources.fuel {
store.add_fuel(fuel).map_err(|e| Error::InstanceCreation(
format!("Failed to add fuel: {}", e)
))?;
}
}
let mut linker = Linker::new(&self.engine);
wasmtime_wasi::add_to_linker(&mut linker, |s| &mut s.wasi_ctx)
.map_err(|e| Error::InstanceCreation(
format!("Failed to add WASI to linker: {}", e)
))?;
{
let mut metrics = self.metrics.lock().unwrap();
metrics.active_instances += 1;
}
let instance = WasmtimeInstance::new(
store,
Arc::new(linker),
wasmtime_module.id(),
);
Ok(Box::new(instance))
}
fn get_metrics(&self) -> RuntimeMetrics {
self.metrics.lock().unwrap().clone()
}
fn shutdown(&self) -> Result<()> {
Ok(())
}
}