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