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, Instance};
use wasmtime_wasi::{WasiCtx, sync::WasiCtxBuilder};
use crate::error::{Error, Result};
use crate::runtime::{
ModuleId, RuntimeConfig, RuntimeMetrics,
WasmInstance, WasmInstanceState, WasmModule, WasmRuntime
};
use crate::security::{Capabilities, ResourceLimits};
use crate::runtime::wasm_common::{ToWasmValues, FromWasmValues};
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 mut exports = Vec::new();
for export in module.exports() {
if let Some(name) = export.name() {
exports.push(name.to_string());
}
}
Self {
id: ModuleId::new(),
name: None,
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,
memory: Option<Memory>,
}
pub struct WasmtimeInstance {
store: Store<WasmtimeStoreData>,
instance: Instance,
module_id: ModuleId,
}
impl WasmtimeInstance {
pub fn new(
mut store: Store<WasmtimeStoreData>,
instance: Instance,
module_id: ModuleId,
) -> Result<Self> {
let memory = instance
.get_export(&mut store, "memory")
.and_then(|ext| ext.into_memory());
store.data_mut().memory = memory;
store.data_mut().state = WasmInstanceState::Running;
Ok(Self {
store,
instance,
module_id,
})
}
fn get_memory(&self) -> Option<Memory> {
self.store.data().memory
}
}
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 func = match self.instance.get_func(&mut self.store.as_context_mut(), function_name) {
Some(f) => f,
None => {
return Err(Error::FunctionCall {
function_name: function_name.to_string(),
reason: "Function not found in instance".to_string(),
})
}
};
let param_bytes = params.to_wasm_values();
let mut results = vec![Val::I32(0)];
match func.call(&mut self.store.as_context_mut(), &[Val::I32(param_bytes.len() as i32)], &mut results) {
Ok(_) => {
let result_ptr = match results[0] {
Val::I32(ptr) => ptr as u32,
_ => {
return Err(Error::FunctionCall {
function_name: function_name.to_string(),
reason: "Function returned an invalid result type".to_string(),
});
}
};
let memory = match self.get_memory() {
Some(mem) => mem,
None => {
return Err(Error::FunctionCall {
function_name: function_name.to_string(),
reason: "No memory available to read result".to_string(),
});
}
};
let mut length_bytes = [0u8; 4];
memory.data(&self.store)
.get(result_ptr as usize..(result_ptr as usize + 4))
.ok_or_else(|| Error::FunctionCall {
function_name: function_name.to_string(),
reason: "Failed to read result length from memory".to_string(),
})?
.copy_to_slice(&mut length_bytes);
let result_len = u32::from_le_bytes(length_bytes) as usize;
let mut result_data = vec![0u8; result_len];
memory.data(&self.store)
.get((result_ptr as usize + 4)..(result_ptr as usize + 4 + result_len))
.ok_or_else(|| Error::FunctionCall {
function_name: function_name.to_string(),
reason: "Failed to read result data from memory".to_string(),
})?
.copy_to_slice(&mut result_data);
let result: R = FromWasmValues::from_wasm_values(&result_data)?;
Ok(result)
},
Err(e) => {
Err(Error::FunctionCall {
function_name: function_name.to_string(),
reason: format!("Function call failed: {}", e),
})
}
}
}
fn memory_usage(&self) -> usize {
if let Some(memory) = self.get_memory() {
return memory.data_size(&self.store);
}
0
}
fn fuel_usage(&self) -> Option<u64> {
match wasmtime::Fuel::get(&mut self.store.as_context_mut()) {
Ok(Some(fuel_consumed)) => Some(fuel_consumed),
_ => None,
}
}
fn reset_fuel(&self) -> Result<()> {
match wasmtime::Fuel::set(&mut self.store.as_context_mut(), 0) {
Ok(_) => Ok(()),
Err(e) => Err(Error::ResourceLimit(format!("Failed to reset fuel: {}", e))),
}
}
fn add_fuel(&self, fuel: u64) -> Result<()> {
match wasmtime::Fuel::add(&mut self.store.as_context_mut(), fuel) {
Ok(_) => Ok(()),
Err(e) => Err(Error::ResourceLimit(format!("Failed to add fuel: {}", e))),
}
}
unsafe fn memory_ptr(&self) -> Result<*mut u8> {
if let Some(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()
}
}
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);
}
wasmtime_config.static_memory_maximum_size(config.enable_memory_limits.then_some(0x1_0000_0000));
if config.debug_info {
wasmtime_config.debug_info(true);
}
if config.compilation_threads > 0 {
wasmtime_config.cranelift_opt_level(wasmtime::OptLevel::Speed);
wasmtime_config.parallel_compilation(true);
}
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 = Arc::new(WasmtimeModule::new(module, wasm_bytes));
let id = module.id();
self.modules.insert(id, module.clone());
Ok(Box::new(WasmtimeModule {
id,
name: module.name.clone(),
module: module.module.clone(),
exports: module.exports.clone(),
size: module.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 clone: Box<dyn WasmModule> = Box::new(WasmtimeModule {
id: module.id,
name: module.name.clone(),
module: module.module.clone(),
exports: module.exports.clone(),
size: module.size,
});
Ok(Arc::from(clone))
}
fn create_instance(
&self,
module: &dyn WasmModule,
resources: ResourceLimits,
capabilities: Capabilities,
) -> Result<Box<dyn WasmInstance>> {
let wasmtime_module = if let Some(id) = self.modules.iter().find_map(|m| {
if m.id() == module.id() {
Some(m.id())
} else {
None
}
}) {
self.modules.get(&id).unwrap().clone()
} else {
return Err(Error::InstanceCreation(
"Module is not a valid 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 = 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 = wasi_builder.env(&k, &v);
}
},
crate::security::EnvironmentCapability::Full => {
for (k, v) in std::env::vars() {
wasi_builder = wasi_builder.env(&k, &v);
}
},
}
for dir in &capabilities.filesystem.readable_dirs {
wasi_builder = 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,
memory: None,
}
);
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 instance = linker
.instantiate(&mut store, &wasmtime_module.module)
.map_err(|e| Error::InstanceCreation(
format!("Failed to instantiate module: {}", e)
))?;
let instance = WasmtimeInstance::new(
store,
instance,
wasmtime_module.id,
)?;
{
let mut metrics = self.metrics.lock().unwrap();
metrics.active_instances += 1;
}
Ok(Box::new(instance))
}
fn get_metrics(&self) -> RuntimeMetrics {
self.metrics.lock().unwrap().clone()
}
fn shutdown(&self) -> Result<()> {
Ok(())
}
}