use std::sync::LazyLock;
use linera_base::data_types::{Bytecode, StreamUpdate};
use linera_witty::{wasmtime::EntrypointInstance, ExportTo};
use tokio::sync::Mutex;
use tracing::instrument;
use wasmtime::{Config, Engine, Linker, Module, Store};
use super::{
add_metering,
module_cache::ModuleCache,
runtime_api::{BaseRuntimeApi, ContractRuntimeApi, RuntimeApiData, ServiceRuntimeApi},
ContractEntrypoints, ServiceEntrypoints, WasmExecutionError,
};
use crate::{
wasm::{WasmContractModule, WasmServiceModule},
ContractRuntime, ExecutionError, ServiceRuntime,
};
static CONTRACT_ENGINE: LazyLock<Engine> = LazyLock::new(|| {
let mut config = Config::default();
config.cranelift_nan_canonicalization(true);
Engine::new(&config).expect("Failed to create Wasmtime `Engine` for contracts")
});
static SERVICE_ENGINE: LazyLock<Engine> = LazyLock::new(Engine::default);
static CONTRACT_CACHE: LazyLock<Mutex<ModuleCache<Module>>> = LazyLock::new(Mutex::default);
static SERVICE_CACHE: LazyLock<Mutex<ModuleCache<Module>>> = LazyLock::new(Mutex::default);
pub(crate) struct WasmtimeContractInstance<Runtime>
where
Runtime: ContractRuntime + 'static,
{
instance: EntrypointInstance<RuntimeApiData<Runtime>>,
}
pub struct WasmtimeServiceInstance<Runtime> {
instance: EntrypointInstance<RuntimeApiData<Runtime>>,
}
impl WasmContractModule {
pub async fn from_wasmtime(contract_bytecode: Bytecode) -> Result<Self, WasmExecutionError> {
let mut contract_cache = CONTRACT_CACHE.lock().await;
let module = contract_cache
.get_or_insert_with(contract_bytecode, "contract", |bytecode| {
let metered_bytecode = add_metering(bytecode)?;
Module::new(&CONTRACT_ENGINE, metered_bytecode)
})
.map_err(WasmExecutionError::LoadContractModule)?;
Ok(WasmContractModule::Wasmtime { module })
}
}
impl<Runtime> WasmtimeContractInstance<Runtime>
where
Runtime: ContractRuntime + 'static,
{
pub fn prepare(contract_module: &Module, runtime: Runtime) -> Result<Self, WasmExecutionError> {
let mut linker = Linker::new(&CONTRACT_ENGINE);
BaseRuntimeApi::export_to(&mut linker)?;
ContractRuntimeApi::export_to(&mut linker)?;
let user_data = RuntimeApiData::new(runtime);
let mut store = Store::new(&CONTRACT_ENGINE, user_data);
let instance = linker
.instantiate(&mut store, contract_module)
.map_err(WasmExecutionError::LoadContractModule)?;
Ok(Self {
instance: EntrypointInstance::new(instance, store),
})
}
}
impl WasmServiceModule {
pub async fn from_wasmtime(service_bytecode: Bytecode) -> Result<Self, WasmExecutionError> {
let mut service_cache = SERVICE_CACHE.lock().await;
let module = service_cache
.get_or_insert_with(service_bytecode, "service", |bytecode| {
Module::new(&SERVICE_ENGINE, bytecode)
})
.map_err(WasmExecutionError::LoadServiceModule)?;
Ok(WasmServiceModule::Wasmtime { module })
}
}
impl<Runtime> WasmtimeServiceInstance<Runtime>
where
Runtime: ServiceRuntime + 'static,
{
pub fn prepare(service_module: &Module, runtime: Runtime) -> Result<Self, WasmExecutionError> {
let mut linker = Linker::new(&SERVICE_ENGINE);
BaseRuntimeApi::export_to(&mut linker)?;
ServiceRuntimeApi::export_to(&mut linker)?;
let user_data = RuntimeApiData::new(runtime);
let mut store = Store::new(&SERVICE_ENGINE, user_data);
let instance = linker
.instantiate(&mut store, service_module)
.map_err(WasmExecutionError::LoadServiceModule)?;
Ok(Self {
instance: EntrypointInstance::new(instance, store),
})
}
}
impl<Runtime> crate::UserContract for WasmtimeContractInstance<Runtime>
where
Runtime: ContractRuntime + 'static,
{
#[instrument(skip_all)]
fn instantiate(&mut self, argument: Vec<u8>) -> Result<(), ExecutionError> {
ContractEntrypoints::new(&mut self.instance)
.instantiate(argument)
.map_err(WasmExecutionError::from)?;
Ok(())
}
#[instrument(skip_all)]
fn execute_operation(&mut self, operation: Vec<u8>) -> Result<Vec<u8>, ExecutionError> {
let result = ContractEntrypoints::new(&mut self.instance)
.execute_operation(operation)
.map_err(WasmExecutionError::from)?;
Ok(result)
}
#[instrument(skip_all)]
fn execute_message(&mut self, message: Vec<u8>) -> Result<(), ExecutionError> {
ContractEntrypoints::new(&mut self.instance)
.execute_message(message)
.map_err(WasmExecutionError::from)?;
Ok(())
}
#[instrument(skip_all)]
fn process_streams(&mut self, updates: Vec<StreamUpdate>) -> Result<(), ExecutionError> {
ContractEntrypoints::new(&mut self.instance)
.process_streams(updates)
.map_err(WasmExecutionError::from)?;
Ok(())
}
#[instrument(skip_all)]
fn finalize(&mut self) -> Result<(), ExecutionError> {
ContractEntrypoints::new(&mut self.instance)
.finalize()
.map_err(WasmExecutionError::from)?;
Ok(())
}
}
impl<Runtime> crate::UserService for WasmtimeServiceInstance<Runtime>
where
Runtime: ServiceRuntime + 'static,
{
fn handle_query(&mut self, argument: Vec<u8>) -> Result<Vec<u8>, ExecutionError> {
Ok(ServiceEntrypoints::new(&mut self.instance)
.handle_query(argument)
.map_err(WasmExecutionError::from)?)
}
}