use std::{marker::Unpin, sync::LazyLock};
use linera_base::data_types::{Bytecode, StreamUpdate};
use linera_witty::{
wasmer::{EntrypointInstance, InstanceBuilder},
ExportTo,
};
use tokio::sync::Mutex;
use tracing::instrument;
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 SERVICE_ENGINE: LazyLock<wasmer::Engine> = LazyLock::new(|| {
#[cfg(web)]
{
wasmer::Engine::default()
}
#[cfg(not(web))]
{
wasmer::sys::EngineBuilder::new(wasmer::Cranelift::new()).into()
}
});
static CONTRACT_ENGINE: LazyLock<wasmer::Engine> = LazyLock::new(|| {
#[cfg(not(web))]
{
let mut compiler_config = wasmer_compiler_singlepass::Singlepass::default();
compiler_config.canonicalize_nans(true);
wasmer::sys::EngineBuilder::new(compiler_config).into()
}
#[cfg(web)]
{
wasmer::Engine::default()
}
});
static CONTRACT_CACHE: LazyLock<Mutex<ModuleCache<wasmer::Module>>> = LazyLock::new(Mutex::default);
static SERVICE_CACHE: LazyLock<Mutex<ModuleCache<wasmer::Module>>> = LazyLock::new(Mutex::default);
pub(crate) struct WasmerContractInstance<Runtime> {
instance: EntrypointInstance<RuntimeApiData<Runtime>>,
}
pub struct WasmerServiceInstance<Runtime> {
instance: EntrypointInstance<RuntimeApiData<Runtime>>,
}
impl WasmContractModule {
pub async fn from_wasmer(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)?;
wasmer::Module::new(&*CONTRACT_ENGINE, metered_bytecode)
.map_err(anyhow::Error::from)
})
.map_err(WasmExecutionError::LoadContractModule)?;
Ok(WasmContractModule::Wasmer {
engine: CONTRACT_ENGINE.clone(),
module,
})
}
}
impl<Runtime> WasmerContractInstance<Runtime>
where
Runtime: ContractRuntime + Clone + Unpin + 'static,
{
pub fn prepare(
contract_engine: wasmer::Engine,
contract_module: &wasmer::Module,
runtime: Runtime,
) -> Result<Self, WasmExecutionError> {
let system_api_data = RuntimeApiData::new(runtime);
let mut instance_builder = InstanceBuilder::new(contract_engine, system_api_data);
BaseRuntimeApi::export_to(&mut instance_builder)?;
ContractRuntimeApi::export_to(&mut instance_builder)?;
let instance = instance_builder.instantiate(contract_module)?;
Ok(Self { instance })
}
}
impl WasmServiceModule {
pub async fn from_wasmer(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| {
wasmer::Module::new(&*SERVICE_ENGINE, bytecode).map_err(anyhow::Error::from)
})
.map_err(WasmExecutionError::LoadServiceModule)?;
Ok(WasmServiceModule::Wasmer { module })
}
}
impl<Runtime> WasmerServiceInstance<Runtime>
where
Runtime: ServiceRuntime + Clone + Unpin + 'static,
{
pub fn prepare(
service_module: &wasmer::Module,
runtime: Runtime,
) -> Result<Self, WasmExecutionError> {
let system_api_data = RuntimeApiData::new(runtime);
let mut instance_builder = InstanceBuilder::new(SERVICE_ENGINE.clone(), system_api_data);
BaseRuntimeApi::export_to(&mut instance_builder)?;
ServiceRuntimeApi::export_to(&mut instance_builder)?;
let instance = instance_builder.instantiate(service_module)?;
Ok(Self { instance })
}
}
impl<Runtime> crate::UserContract for WasmerContractInstance<Runtime>
where
Runtime: ContractRuntime + Unpin + '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> {
Ok(ContractEntrypoints::new(&mut self.instance)
.execute_operation(operation)
.map_err(WasmExecutionError::from)?)
}
#[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: 'static> crate::UserService for WasmerServiceInstance<Runtime> {
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)?)
}
}
impl From<ExecutionError> for wasmer::RuntimeError {
fn from(error: ExecutionError) -> Self {
wasmer::RuntimeError::user(Box::new(error))
}
}
impl From<wasmer::RuntimeError> for ExecutionError {
fn from(error: wasmer::RuntimeError) -> Self {
error
.downcast::<ExecutionError>()
.unwrap_or_else(|unknown_error| {
ExecutionError::WasmError(WasmExecutionError::ExecuteModuleInWasmer(unknown_error))
})
}
}