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