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