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