linera_execution/wasm/
wasmtime.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Code specific to the usage of the [Wasmtime](https://wasmtime.dev/) runtime.
5
6use std::sync::LazyLock;
7
8use linera_base::data_types::{Bytecode, StreamUpdate};
9use linera_witty::{wasmtime::EntrypointInstance, ExportTo};
10use tokio::sync::Mutex;
11use tracing::instrument;
12use wasmtime::{Config, Engine, Linker, Module, Store};
13
14use super::{
15    add_metering,
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 contracts.
26static CONTRACT_ENGINE: LazyLock<Engine> = LazyLock::new(|| {
27    let mut config = Config::default();
28    config.cranelift_nan_canonicalization(true);
29
30    Engine::new(&config).expect("Failed to create Wasmtime `Engine` for contracts")
31});
32
33/// An [`Engine`] instance configured to run application services.
34static SERVICE_ENGINE: LazyLock<Engine> = LazyLock::new(Engine::default);
35
36/// A cache of compiled contract modules.
37static CONTRACT_CACHE: LazyLock<Mutex<ModuleCache<Module>>> = LazyLock::new(Mutex::default);
38
39/// A cache of compiled service modules.
40static SERVICE_CACHE: LazyLock<Mutex<ModuleCache<Module>>> = LazyLock::new(Mutex::default);
41
42/// Type representing a running [Wasmtime](https://wasmtime.dev/) contract.
43///
44/// The runtime has a lifetime so that it does not outlive the trait object used to export the
45/// system API.
46pub(crate) struct WasmtimeContractInstance<Runtime>
47where
48    Runtime: ContractRuntime + 'static,
49{
50    /// The Wasm module instance.
51    instance: EntrypointInstance<RuntimeApiData<Runtime>>,
52}
53
54/// Type representing a running [Wasmtime](https://wasmtime.dev/) service.
55pub struct WasmtimeServiceInstance<Runtime> {
56    /// The Wasm module instance.
57    instance: EntrypointInstance<RuntimeApiData<Runtime>>,
58}
59
60impl WasmContractModule {
61    /// Creates a new [`WasmContractModule`] using Wasmtime with the provided bytecode files.
62    pub async fn from_wasmtime(contract_bytecode: Bytecode) -> Result<Self, WasmExecutionError> {
63        let mut contract_cache = CONTRACT_CACHE.lock().await;
64        let module = contract_cache
65            .get_or_insert_with(contract_bytecode, "contract", |bytecode| {
66                let metered_bytecode = add_metering(&bytecode)?;
67                Module::new(&CONTRACT_ENGINE, metered_bytecode)
68            })
69            .map_err(WasmExecutionError::LoadContractModule)?;
70        Ok(WasmContractModule::Wasmtime { module })
71    }
72}
73
74impl<Runtime> WasmtimeContractInstance<Runtime>
75where
76    Runtime: ContractRuntime + 'static,
77{
78    /// Prepares a runtime instance to call into the Wasm contract.
79    pub fn prepare(contract_module: &Module, runtime: Runtime) -> Result<Self, WasmExecutionError> {
80        let mut linker = Linker::new(&CONTRACT_ENGINE);
81
82        BaseRuntimeApi::export_to(&mut linker)?;
83        ContractRuntimeApi::export_to(&mut linker)?;
84
85        let user_data = RuntimeApiData::new(runtime);
86        let mut store = Store::new(&CONTRACT_ENGINE, user_data);
87        let instance = linker
88            .instantiate(&mut store, contract_module)
89            .map_err(WasmExecutionError::LoadContractModule)?;
90
91        Ok(Self {
92            instance: EntrypointInstance::new(instance, store),
93        })
94    }
95}
96
97impl WasmServiceModule {
98    /// Creates a new [`WasmServiceModule`] using Wasmtime with the provided bytecode files.
99    pub async fn from_wasmtime(service_bytecode: Bytecode) -> Result<Self, WasmExecutionError> {
100        let mut service_cache = SERVICE_CACHE.lock().await;
101        let module = service_cache
102            .get_or_insert_with(service_bytecode, "service", |bytecode| {
103                Module::new(&SERVICE_ENGINE, bytecode)
104            })
105            .map_err(WasmExecutionError::LoadServiceModule)?;
106        Ok(WasmServiceModule::Wasmtime { module })
107    }
108}
109
110impl<Runtime> WasmtimeServiceInstance<Runtime>
111where
112    Runtime: ServiceRuntime + 'static,
113{
114    /// Prepares a runtime instance to call into the Wasm service.
115    pub fn prepare(service_module: &Module, runtime: Runtime) -> Result<Self, WasmExecutionError> {
116        let mut linker = Linker::new(&SERVICE_ENGINE);
117
118        BaseRuntimeApi::export_to(&mut linker)?;
119        ServiceRuntimeApi::export_to(&mut linker)?;
120
121        let user_data = RuntimeApiData::new(runtime);
122        let mut store = Store::new(&SERVICE_ENGINE, user_data);
123        let instance = linker
124            .instantiate(&mut store, service_module)
125            .map_err(WasmExecutionError::LoadServiceModule)?;
126
127        Ok(Self {
128            instance: EntrypointInstance::new(instance, store),
129        })
130    }
131}
132
133impl<Runtime> crate::UserContract for WasmtimeContractInstance<Runtime>
134where
135    Runtime: ContractRuntime + 'static,
136{
137    #[instrument(skip_all)]
138    fn instantiate(&mut self, argument: Vec<u8>) -> Result<(), ExecutionError> {
139        ContractEntrypoints::new(&mut self.instance)
140            .instantiate(argument)
141            .map_err(WasmExecutionError::from)?;
142        Ok(())
143    }
144
145    #[instrument(skip_all)]
146    fn execute_operation(&mut self, operation: Vec<u8>) -> Result<Vec<u8>, ExecutionError> {
147        let result = ContractEntrypoints::new(&mut self.instance)
148            .execute_operation(operation)
149            .map_err(WasmExecutionError::from)?;
150        Ok(result)
151    }
152
153    #[instrument(skip_all)]
154    fn execute_message(&mut self, message: Vec<u8>) -> Result<(), ExecutionError> {
155        ContractEntrypoints::new(&mut self.instance)
156            .execute_message(message)
157            .map_err(WasmExecutionError::from)?;
158        Ok(())
159    }
160
161    #[instrument(skip_all)]
162    fn process_streams(&mut self, updates: Vec<StreamUpdate>) -> Result<(), ExecutionError> {
163        ContractEntrypoints::new(&mut self.instance)
164            .process_streams(updates)
165            .map_err(WasmExecutionError::from)?;
166        Ok(())
167    }
168
169    #[instrument(skip_all)]
170    fn finalize(&mut self) -> Result<(), ExecutionError> {
171        ContractEntrypoints::new(&mut self.instance)
172            .finalize()
173            .map_err(WasmExecutionError::from)?;
174        Ok(())
175    }
176}
177
178impl<Runtime> crate::UserService for WasmtimeServiceInstance<Runtime>
179where
180    Runtime: ServiceRuntime + 'static,
181{
182    fn handle_query(&mut self, argument: Vec<u8>) -> Result<Vec<u8>, ExecutionError> {
183        Ok(ServiceEntrypoints::new(&mut self.instance)
184            .handle_query(argument)
185            .map_err(WasmExecutionError::from)?)
186    }
187}