linera_execution/wasm/
wasmer.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Code specific to the usage of the [Wasmer](https://wasmer.io/) runtime.
5
6use std::{marker::Unpin, sync::LazyLock};
7
8use linera_base::data_types::{Bytecode, StreamUpdate};
9use linera_witty::{
10    wasmer::{EntrypointInstance, InstanceBuilder},
11    ExportTo,
12};
13use tokio::sync::Mutex;
14use tracing::instrument;
15
16use super::{
17    module_cache::ModuleCache,
18    runtime_api::{BaseRuntimeApi, ContractRuntimeApi, RuntimeApiData, ServiceRuntimeApi},
19    ContractEntrypoints, ServiceEntrypoints, WasmExecutionError,
20};
21use crate::{
22    wasm::{WasmContractModule, WasmServiceModule},
23    ContractRuntime, ExecutionError, ServiceRuntime,
24};
25
26/// An [`Engine`] instance configured to run application services.
27static SERVICE_ENGINE: LazyLock<wasmer::Engine> = LazyLock::new(|| {
28    #[cfg(web)]
29    {
30        wasmer::Engine::default()
31    }
32
33    #[cfg(not(web))]
34    {
35        wasmer::sys::EngineBuilder::new(wasmer::Cranelift::new()).into()
36    }
37});
38
39/// A cache of compiled contract modules, with their respective [`wasmer::Engine`] instances.
40static CONTRACT_CACHE: LazyLock<Mutex<ModuleCache<CachedContractModule>>> =
41    LazyLock::new(Mutex::default);
42
43/// A cache of compiled service modules.
44static SERVICE_CACHE: LazyLock<Mutex<ModuleCache<wasmer::Module>>> = LazyLock::new(Mutex::default);
45
46/// Type representing a running [Wasmer](https://wasmer.io/) contract.
47pub(crate) struct WasmerContractInstance<Runtime> {
48    /// The Wasmer instance.
49    instance: EntrypointInstance<RuntimeApiData<Runtime>>,
50}
51
52/// Type representing a running [Wasmer](https://wasmer.io/) service.
53pub struct WasmerServiceInstance<Runtime> {
54    /// The Wasmer instance.
55    instance: EntrypointInstance<RuntimeApiData<Runtime>>,
56}
57
58impl WasmContractModule {
59    /// Creates a new [`WasmContractModule`] using Wasmer with the provided bytecode files.
60    pub async fn from_wasmer(contract_bytecode: Bytecode) -> Result<Self, WasmExecutionError> {
61        let mut contract_cache = CONTRACT_CACHE.lock().await;
62        let (engine, module) = contract_cache
63            .get_or_insert_with(contract_bytecode, CachedContractModule::new)
64            .map_err(WasmExecutionError::LoadContractModule)?
65            .create_execution_instance()
66            .map_err(WasmExecutionError::LoadContractModule)?;
67        Ok(WasmContractModule::Wasmer { engine, module })
68    }
69}
70
71impl<Runtime> WasmerContractInstance<Runtime>
72where
73    Runtime: ContractRuntime + Clone + Unpin + 'static,
74{
75    /// Prepares a runtime instance to call into the Wasm contract.
76    pub fn prepare(
77        contract_engine: wasmer::Engine,
78        contract_module: &wasmer::Module,
79        runtime: Runtime,
80    ) -> Result<Self, WasmExecutionError> {
81        let system_api_data = RuntimeApiData::new(runtime);
82        let mut instance_builder = InstanceBuilder::new(contract_engine, system_api_data);
83
84        BaseRuntimeApi::export_to(&mut instance_builder)?;
85        ContractRuntimeApi::export_to(&mut instance_builder)?;
86
87        let instance = instance_builder.instantiate(contract_module)?;
88
89        Ok(Self { instance })
90    }
91}
92
93impl WasmServiceModule {
94    /// Creates a new [`WasmServiceModule`] using Wasmer with the provided bytecode files.
95    pub async fn from_wasmer(service_bytecode: Bytecode) -> Result<Self, WasmExecutionError> {
96        let mut service_cache = SERVICE_CACHE.lock().await;
97        let module = service_cache
98            .get_or_insert_with(service_bytecode, |bytecode| {
99                wasmer::Module::new(&*SERVICE_ENGINE, bytecode).map_err(anyhow::Error::from)
100            })
101            .map_err(WasmExecutionError::LoadServiceModule)?;
102        Ok(WasmServiceModule::Wasmer { module })
103    }
104}
105
106impl<Runtime> WasmerServiceInstance<Runtime>
107where
108    Runtime: ServiceRuntime + Clone + Unpin + 'static,
109{
110    /// Prepares a runtime instance to call into the Wasm service.
111    pub fn prepare(
112        service_module: &wasmer::Module,
113        runtime: Runtime,
114    ) -> Result<Self, WasmExecutionError> {
115        let system_api_data = RuntimeApiData::new(runtime);
116        let mut instance_builder = InstanceBuilder::new(SERVICE_ENGINE.clone(), system_api_data);
117
118        BaseRuntimeApi::export_to(&mut instance_builder)?;
119        ServiceRuntimeApi::export_to(&mut instance_builder)?;
120
121        let instance = instance_builder.instantiate(service_module)?;
122
123        Ok(Self { instance })
124    }
125}
126
127impl<Runtime> crate::UserContract for WasmerContractInstance<Runtime>
128where
129    Runtime: ContractRuntime + Unpin + 'static,
130{
131    #[instrument(skip_all)]
132    fn instantiate(&mut self, argument: Vec<u8>) -> Result<(), ExecutionError> {
133        ContractEntrypoints::new(&mut self.instance)
134            .instantiate(argument)
135            .map_err(WasmExecutionError::from)?;
136        Ok(())
137    }
138
139    #[instrument(skip_all)]
140    fn execute_operation(&mut self, operation: Vec<u8>) -> Result<Vec<u8>, ExecutionError> {
141        Ok(ContractEntrypoints::new(&mut self.instance)
142            .execute_operation(operation)
143            .map_err(WasmExecutionError::from)?)
144    }
145
146    #[instrument(skip_all)]
147    fn execute_message(&mut self, message: Vec<u8>) -> Result<(), ExecutionError> {
148        ContractEntrypoints::new(&mut self.instance)
149            .execute_message(message)
150            .map_err(WasmExecutionError::from)?;
151        Ok(())
152    }
153
154    #[instrument(skip_all)]
155    fn process_streams(&mut self, updates: Vec<StreamUpdate>) -> Result<(), ExecutionError> {
156        ContractEntrypoints::new(&mut self.instance)
157            .process_streams(updates)
158            .map_err(WasmExecutionError::from)?;
159        Ok(())
160    }
161
162    #[instrument(skip_all)]
163    fn finalize(&mut self) -> Result<(), ExecutionError> {
164        ContractEntrypoints::new(&mut self.instance)
165            .finalize()
166            .map_err(WasmExecutionError::from)?;
167        Ok(())
168    }
169}
170
171impl<Runtime: 'static> crate::UserService for WasmerServiceInstance<Runtime> {
172    fn handle_query(&mut self, argument: Vec<u8>) -> Result<Vec<u8>, ExecutionError> {
173        Ok(ServiceEntrypoints::new(&mut self.instance)
174            .handle_query(argument)
175            .map_err(WasmExecutionError::from)?)
176    }
177}
178
179impl From<ExecutionError> for wasmer::RuntimeError {
180    fn from(error: ExecutionError) -> Self {
181        wasmer::RuntimeError::user(Box::new(error))
182    }
183}
184
185impl From<wasmer::RuntimeError> for ExecutionError {
186    fn from(error: wasmer::RuntimeError) -> Self {
187        error
188            .downcast::<ExecutionError>()
189            .unwrap_or_else(|unknown_error| {
190                ExecutionError::WasmError(WasmExecutionError::ExecuteModuleInWasmer(unknown_error))
191            })
192    }
193}
194
195/// Serialized bytes of a compiled contract bytecode.
196// Cloning `Module`s is cheap.
197#[derive(Clone)]
198pub struct CachedContractModule(wasmer::Module);
199
200impl CachedContractModule {
201    /// Creates a new [`CachedContractModule`] by compiling a `contract_bytecode`.
202    pub fn new(contract_bytecode: Bytecode) -> Result<Self, anyhow::Error> {
203        let module = wasmer::Module::new(&Self::create_compilation_engine(), contract_bytecode)?;
204        Ok(CachedContractModule(module))
205    }
206
207    /// Creates a new [`Engine`] to compile a contract bytecode.
208    fn create_compilation_engine() -> wasmer::Engine {
209        #[cfg(not(web))]
210        {
211            let mut compiler_config = wasmer_compiler_singlepass::Singlepass::default();
212            compiler_config.canonicalize_nans(true);
213
214            wasmer::sys::EngineBuilder::new(compiler_config).into()
215        }
216
217        #[cfg(web)]
218        wasmer::Engine::default()
219    }
220
221    /// Creates a [`Module`] from a compiled contract using a headless [`Engine`].
222    pub fn create_execution_instance(
223        &self,
224    ) -> Result<(wasmer::Engine, wasmer::Module), anyhow::Error> {
225        #[cfg(web)]
226        {
227            Ok((wasmer::Engine::default(), self.0.clone()))
228        }
229
230        #[cfg(not(web))]
231        {
232            let engine = wasmer::Engine::default();
233            let store = wasmer::Store::new(engine.clone());
234            let bytes = self.0.serialize()?;
235            let module = unsafe { wasmer::Module::deserialize(&store, bytes) }?;
236            Ok((engine, module))
237        }
238    }
239}