/__w/smoldot/smoldot/repo/lib/src/executor/vm/jit.rs
Line | Count | Source |
1 | | // Smoldot |
2 | | // Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. |
3 | | // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 |
4 | | |
5 | | // This program is free software: you can redistribute it and/or modify |
6 | | // it under the terms of the GNU General Public License as published by |
7 | | // the Free Software Foundation, either version 3 of the License, or |
8 | | // (at your option) any later version. |
9 | | |
10 | | // This program is distributed in the hope that it will be useful, |
11 | | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | | // GNU General Public License for more details. |
14 | | |
15 | | // You should have received a copy of the GNU General Public License |
16 | | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
17 | | |
18 | | //! Implements the API documented [in the parent module](..). |
19 | | |
20 | | use super::{ |
21 | | ExecOutcome, GlobalValueErr, HeapPages, NewErr, OutOfBoundsError, RunErr, Signature, StartErr, |
22 | | Trap, ValueType, WasmValue, |
23 | | }; |
24 | | |
25 | | use alloc::{boxed::Box, sync::Arc, vec::Vec}; |
26 | | use core::{fmt, future, mem, pin, slice, task}; |
27 | | // TODO: we use std::sync::Mutex rather than parking_lot::Mutex due to issues with Cargo features, see <https://github.com/paritytech/smoldot/issues/2732> |
28 | | use std::sync::Mutex; |
29 | | |
30 | | /// See [`super::VirtualMachinePrototype`]. |
31 | | pub struct JitPrototype { |
32 | | /// Base components that can be used to recreate a prototype later if desired. |
33 | | base_components: BaseComponents, |
34 | | |
35 | | store: wasmtime::Store<()>, |
36 | | |
37 | | /// Instantiated Wasm VM. |
38 | | instance: wasmtime::Instance, |
39 | | |
40 | | /// Shared between the "outside" and the external functions. See [`Shared`]. |
41 | | shared: Arc<Mutex<Shared>>, |
42 | | |
43 | | /// Reference to the memory used by the module. |
44 | | memory: wasmtime::Memory, |
45 | | |
46 | | /// The type associated with [`JitPrototype`]. |
47 | | memory_type: wasmtime::MemoryType, |
48 | | } |
49 | | |
50 | | struct BaseComponents { |
51 | | module: wasmtime::Module, |
52 | | |
53 | | /// For each import of the module, either `None` if not a function, or `Some` containing the |
54 | | /// `usize` of that function. |
55 | | resolved_imports: Vec<Option<usize>>, |
56 | | } |
57 | | |
58 | | impl JitPrototype { |
59 | | /// See [`super::VirtualMachinePrototype::new`]. |
60 | 92 | pub fn new( |
61 | 92 | module_bytes: &[u8], |
62 | 92 | symbols: &mut dyn FnMut(&str, &str, &Signature) -> Result<usize, ()>, |
63 | 92 | ) -> Result<Self, NewErr> { |
64 | 92 | let mut config = wasmtime::Config::new(); |
65 | 92 | config.cranelift_nan_canonicalization(true); |
66 | 92 | config.cranelift_opt_level(wasmtime::OptLevel::Speed); |
67 | 92 | config.async_support(true); |
68 | | // The default value of `wasm_backtrace_details` is `Environment`, which reads the |
69 | | // `WASMTIME_BACKTRACE_DETAILS` environment variable to determine whether or not to keep |
70 | | // debug info. However we don't want any of the behaviour of our code to rely on any |
71 | | // environment variables whatsoever. Whether to use `Enable` or `Disable` below isn't |
72 | | // very important, so long as it is not `Environment`. |
73 | 92 | config.wasm_backtrace_details(wasmtime::WasmBacktraceDetails::Enable); |
74 | | |
75 | | // Disable all post-MVP wasm features. |
76 | | // Some of these configuration options are `true` by default while some others are `false` |
77 | | // by default, but we just disable them all to be sure. |
78 | 92 | config.wasm_threads(false); |
79 | 92 | config.wasm_reference_types(false); |
80 | 92 | config.wasm_function_references(false); |
81 | 92 | config.wasm_simd(false); |
82 | 92 | config.wasm_relaxed_simd(false); |
83 | 92 | config.wasm_bulk_memory(false); |
84 | 92 | config.wasm_multi_value(false); |
85 | 92 | config.wasm_multi_memory(false); |
86 | 92 | config.wasm_memory64(false); |
87 | 92 | config.wasm_tail_call(false); |
88 | 92 | config.wasm_component_model(false); |
89 | 92 | config.wasm_wide_arithmetic(false); |
90 | 92 | config.wasm_extended_const(false); |
91 | | |
92 | 92 | let engine = |
93 | 92 | wasmtime::Engine::new(&config).map_err(|err| NewErr::InvalidWasm(err0 .to_string0 ()))?0 ; Unexecuted instantiation: _RNCNvMNtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB4_12JitPrototype3new0Ba_ Unexecuted instantiation: _RNCNvMNtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB4_12JitPrototype3new0Ba_ |
94 | | |
95 | 92 | let module82 = wasmtime::Module::from_binary(&engine, module_bytes) |
96 | 92 | .map_err(|err| NewErr::InvalidWasm(err10 .to_string10 ()))?10 ; _RNCNvMNtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB4_12JitPrototype3news_0Ba_ Line | Count | Source | 96 | 10 | .map_err(|err| NewErr::InvalidWasm(err.to_string()))?; |
Unexecuted instantiation: _RNCNvMNtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB4_12JitPrototype3news_0Ba_ |
97 | | |
98 | | // Building the list of imports that the Wasm VM is able to use. |
99 | 76 | let resolved_imports = { |
100 | 82 | let mut imports = Vec::with_capacity(module.imports().len()); |
101 | 891 | for import in module82 .imports82 () { |
102 | 891 | match import.ty() { |
103 | 833 | wasmtime::ExternType::Func(func_type) => { |
104 | | // Note that if `Signature::try_from` fails, a `UnresolvedFunctionImport` is |
105 | | // also returned. This is because it is not possible for the function to |
106 | | // resolve anyway if its signature can't be represented. |
107 | 828 | let function_index = |
108 | 833 | match Signature::try_from(&func_type) |
109 | 833 | .ok() |
110 | 833 | .and_then(|conv_signature| {832 |
111 | 832 | symbols(import.module(), import.name(), &conv_signature).ok() |
112 | 832 | }) { _RNCNvMNtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB4_12JitPrototype3news0_0Ba_ Line | Count | Source | 110 | 165 | .and_then(|conv_signature| { | 111 | 165 | symbols(import.module(), import.name(), &conv_signature).ok() | 112 | 165 | }) { |
_RNCNvMNtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB4_12JitPrototype3news0_0Ba_ Line | Count | Source | 110 | 667 | .and_then(|conv_signature| { | 111 | 667 | symbols(import.module(), import.name(), &conv_signature).ok() | 112 | 667 | }) { |
|
113 | 828 | Some(i) => i, |
114 | | None => { |
115 | 5 | return Err(NewErr::UnresolvedFunctionImport { |
116 | 5 | module_name: import.module().to_owned(), |
117 | 5 | function: import.name().to_owned(), |
118 | 5 | }); |
119 | | } |
120 | | }; |
121 | | |
122 | 828 | imports.push(Some(function_index)); |
123 | | } |
124 | | wasmtime::ExternType::Global(_) | wasmtime::ExternType::Table(_) => { |
125 | 1 | return Err(NewErr::ImportTypeNotSupported); |
126 | | } |
127 | 57 | wasmtime::ExternType::Memory(_) => { |
128 | 57 | imports.push(None); |
129 | 57 | } |
130 | | }; |
131 | | } |
132 | 76 | imports |
133 | | }; |
134 | | |
135 | 76 | Self::from_base_components(BaseComponents { |
136 | 76 | module, |
137 | 76 | resolved_imports, |
138 | 76 | }) |
139 | 92 | } _RNvMNtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB2_12JitPrototype3new Line | Count | Source | 60 | 69 | pub fn new( | 61 | 69 | module_bytes: &[u8], | 62 | 69 | symbols: &mut dyn FnMut(&str, &str, &Signature) -> Result<usize, ()>, | 63 | 69 | ) -> Result<Self, NewErr> { | 64 | 69 | let mut config = wasmtime::Config::new(); | 65 | 69 | config.cranelift_nan_canonicalization(true); | 66 | 69 | config.cranelift_opt_level(wasmtime::OptLevel::Speed); | 67 | 69 | config.async_support(true); | 68 | | // The default value of `wasm_backtrace_details` is `Environment`, which reads the | 69 | | // `WASMTIME_BACKTRACE_DETAILS` environment variable to determine whether or not to keep | 70 | | // debug info. However we don't want any of the behaviour of our code to rely on any | 71 | | // environment variables whatsoever. Whether to use `Enable` or `Disable` below isn't | 72 | | // very important, so long as it is not `Environment`. | 73 | 69 | config.wasm_backtrace_details(wasmtime::WasmBacktraceDetails::Enable); | 74 | | | 75 | | // Disable all post-MVP wasm features. | 76 | | // Some of these configuration options are `true` by default while some others are `false` | 77 | | // by default, but we just disable them all to be sure. | 78 | 69 | config.wasm_threads(false); | 79 | 69 | config.wasm_reference_types(false); | 80 | 69 | config.wasm_function_references(false); | 81 | 69 | config.wasm_simd(false); | 82 | 69 | config.wasm_relaxed_simd(false); | 83 | 69 | config.wasm_bulk_memory(false); | 84 | 69 | config.wasm_multi_value(false); | 85 | 69 | config.wasm_multi_memory(false); | 86 | 69 | config.wasm_memory64(false); | 87 | 69 | config.wasm_tail_call(false); | 88 | 69 | config.wasm_component_model(false); | 89 | 69 | config.wasm_wide_arithmetic(false); | 90 | 69 | config.wasm_extended_const(false); | 91 | | | 92 | 69 | let engine = | 93 | 69 | wasmtime::Engine::new(&config).map_err(|err| NewErr::InvalidWasm(err.to_string()))?0 ; | 94 | | | 95 | 69 | let module59 = wasmtime::Module::from_binary(&engine, module_bytes) | 96 | 69 | .map_err(|err| NewErr::InvalidWasm(err.to_string()))?10 ; | 97 | | | 98 | | // Building the list of imports that the Wasm VM is able to use. | 99 | 53 | let resolved_imports = { | 100 | 59 | let mut imports = Vec::with_capacity(module.imports().len()); | 101 | 201 | for import in module59 .imports59 () { | 102 | 201 | match import.ty() { | 103 | 166 | wasmtime::ExternType::Func(func_type) => { | 104 | | // Note that if `Signature::try_from` fails, a `UnresolvedFunctionImport` is | 105 | | // also returned. This is because it is not possible for the function to | 106 | | // resolve anyway if its signature can't be represented. | 107 | 161 | let function_index = | 108 | 166 | match Signature::try_from(&func_type) | 109 | 166 | .ok() | 110 | 166 | .and_then(|conv_signature| { | 111 | | symbols(import.module(), import.name(), &conv_signature).ok() | 112 | | }) { | 113 | 161 | Some(i) => i, | 114 | | None => { | 115 | 5 | return Err(NewErr::UnresolvedFunctionImport { | 116 | 5 | module_name: import.module().to_owned(), | 117 | 5 | function: import.name().to_owned(), | 118 | 5 | }); | 119 | | } | 120 | | }; | 121 | | | 122 | 161 | imports.push(Some(function_index)); | 123 | | } | 124 | | wasmtime::ExternType::Global(_) | wasmtime::ExternType::Table(_) => { | 125 | 1 | return Err(NewErr::ImportTypeNotSupported); | 126 | | } | 127 | 34 | wasmtime::ExternType::Memory(_) => { | 128 | 34 | imports.push(None); | 129 | 34 | } | 130 | | }; | 131 | | } | 132 | 53 | imports | 133 | | }; | 134 | | | 135 | 53 | Self::from_base_components(BaseComponents { | 136 | 53 | module, | 137 | 53 | resolved_imports, | 138 | 53 | }) | 139 | 69 | } |
_RNvMNtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB2_12JitPrototype3new Line | Count | Source | 60 | 23 | pub fn new( | 61 | 23 | module_bytes: &[u8], | 62 | 23 | symbols: &mut dyn FnMut(&str, &str, &Signature) -> Result<usize, ()>, | 63 | 23 | ) -> Result<Self, NewErr> { | 64 | 23 | let mut config = wasmtime::Config::new(); | 65 | 23 | config.cranelift_nan_canonicalization(true); | 66 | 23 | config.cranelift_opt_level(wasmtime::OptLevel::Speed); | 67 | 23 | config.async_support(true); | 68 | | // The default value of `wasm_backtrace_details` is `Environment`, which reads the | 69 | | // `WASMTIME_BACKTRACE_DETAILS` environment variable to determine whether or not to keep | 70 | | // debug info. However we don't want any of the behaviour of our code to rely on any | 71 | | // environment variables whatsoever. Whether to use `Enable` or `Disable` below isn't | 72 | | // very important, so long as it is not `Environment`. | 73 | 23 | config.wasm_backtrace_details(wasmtime::WasmBacktraceDetails::Enable); | 74 | | | 75 | | // Disable all post-MVP wasm features. | 76 | | // Some of these configuration options are `true` by default while some others are `false` | 77 | | // by default, but we just disable them all to be sure. | 78 | 23 | config.wasm_threads(false); | 79 | 23 | config.wasm_reference_types(false); | 80 | 23 | config.wasm_function_references(false); | 81 | 23 | config.wasm_simd(false); | 82 | 23 | config.wasm_relaxed_simd(false); | 83 | 23 | config.wasm_bulk_memory(false); | 84 | 23 | config.wasm_multi_value(false); | 85 | 23 | config.wasm_multi_memory(false); | 86 | 23 | config.wasm_memory64(false); | 87 | 23 | config.wasm_tail_call(false); | 88 | 23 | config.wasm_component_model(false); | 89 | 23 | config.wasm_wide_arithmetic(false); | 90 | 23 | config.wasm_extended_const(false); | 91 | | | 92 | 23 | let engine = | 93 | 23 | wasmtime::Engine::new(&config).map_err(|err| NewErr::InvalidWasm(err.to_string()))?0 ; | 94 | | | 95 | 23 | let module = wasmtime::Module::from_binary(&engine, module_bytes) | 96 | 23 | .map_err(|err| NewErr::InvalidWasm(err.to_string()))?0 ; | 97 | | | 98 | | // Building the list of imports that the Wasm VM is able to use. | 99 | 23 | let resolved_imports = { | 100 | 23 | let mut imports = Vec::with_capacity(module.imports().len()); | 101 | 690 | for import in module23 .imports23 () { | 102 | 690 | match import.ty() { | 103 | 667 | wasmtime::ExternType::Func(func_type) => { | 104 | | // Note that if `Signature::try_from` fails, a `UnresolvedFunctionImport` is | 105 | | // also returned. This is because it is not possible for the function to | 106 | | // resolve anyway if its signature can't be represented. | 107 | 667 | let function_index = | 108 | 667 | match Signature::try_from(&func_type) | 109 | 667 | .ok() | 110 | 667 | .and_then(|conv_signature| { | 111 | | symbols(import.module(), import.name(), &conv_signature).ok() | 112 | | }) { | 113 | 667 | Some(i) => i, | 114 | | None => { | 115 | 0 | return Err(NewErr::UnresolvedFunctionImport { | 116 | 0 | module_name: import.module().to_owned(), | 117 | 0 | function: import.name().to_owned(), | 118 | 0 | }); | 119 | | } | 120 | | }; | 121 | | | 122 | 667 | imports.push(Some(function_index)); | 123 | | } | 124 | | wasmtime::ExternType::Global(_) | wasmtime::ExternType::Table(_) => { | 125 | 0 | return Err(NewErr::ImportTypeNotSupported); | 126 | | } | 127 | 23 | wasmtime::ExternType::Memory(_) => { | 128 | 23 | imports.push(None); | 129 | 23 | } | 130 | | }; | 131 | | } | 132 | 23 | imports | 133 | | }; | 134 | | | 135 | 23 | Self::from_base_components(BaseComponents { | 136 | 23 | module, | 137 | 23 | resolved_imports, | 138 | 23 | }) | 139 | 23 | } |
|
140 | | |
141 | 85 | fn from_base_components(base_components: BaseComponents) -> Result<Self, NewErr> { |
142 | 85 | let mut store = wasmtime::Store::new(base_components.module.engine(), ()); |
143 | | |
144 | 85 | let mut imported_memory = None; |
145 | 85 | let shared = Arc::new(Mutex::new(Shared::ExecutingStart)); |
146 | | |
147 | | // Building the list of symbols that the Wasm VM is able to use. |
148 | 84 | let imports = { |
149 | 85 | let mut imports = Vec::with_capacity(base_components.module.imports().len()); |
150 | 964 | for (module_import, resolved_function) in base_components |
151 | 85 | .module |
152 | 85 | .imports() |
153 | 85 | .zip(base_components.resolved_imports.iter()) |
154 | | { |
155 | 964 | match module_import.ty() { |
156 | 905 | wasmtime::ExternType::Func(func_type) => { |
157 | 905 | let function_index = resolved_function.unwrap(); |
158 | 905 | let shared = shared.clone(); |
159 | | |
160 | | // Obtain `expected_return_ty`. We know that the type is supported due to |
161 | | // the signature check earlier. |
162 | 905 | let expected_return_ty = func_type |
163 | 905 | .results() |
164 | 905 | .next() |
165 | 905 | .map(|v| ValueType::try_from647 (v647 ).unwrap647 ()); _RNCNvMNtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB4_12JitPrototype20from_base_components0Ba_ Line | Count | Source | 165 | 143 | .map(|v| ValueType::try_from(v).unwrap()); |
_RNCNvMNtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB4_12JitPrototype20from_base_components0Ba_ Line | Count | Source | 165 | 504 | .map(|v| ValueType::try_from(v).unwrap()); |
|
166 | | |
167 | 905 | imports.push(wasmtime::Extern::Func(wasmtime::Func::new_async( |
168 | 905 | &mut store, |
169 | 905 | func_type, |
170 | 4.19k | move |mut caller, params, ret_val| { |
171 | | // This closure is executed whenever the Wasm VM calls a |
172 | | // host function. |
173 | | // While a function call is in progress, only this closure can |
174 | | // have access to the `wasmtime::Store`. For this reason, we use |
175 | | // a small communication protocol with the outside. |
176 | | |
177 | | // Transition `shared` from `OutsideFunctionCall` to |
178 | | // `EnteredFunctionCall`. |
179 | | { |
180 | 4.19k | let mut shared_lock = shared.try_lock().unwrap(); |
181 | 4.19k | match mem::replace(&mut *shared_lock, Shared::Poisoned) { |
182 | 4.19k | Shared::OutsideFunctionCall { memory } => { |
183 | 4.19k | *shared_lock = Shared::EnteredFunctionCall { |
184 | 4.19k | function_index, |
185 | 4.19k | // Because the function signature has been |
186 | 4.19k | // validated at initialization, we can safely |
187 | 4.19k | // convert all the parameter types. |
188 | 4.19k | parameters: params |
189 | 4.19k | .iter() |
190 | 4.19k | .map(TryFrom::try_from) |
191 | 4.19k | .collect::<Result<_, _>>() |
192 | 4.19k | .unwrap(), |
193 | 4.19k | expected_return_ty, |
194 | 4.19k | in_interrupted_waker: None, // Filled below |
195 | 4.19k | memory: SliceRawParts( |
196 | 4.19k | memory.data_ptr(&caller), |
197 | 4.19k | memory.data_size(&caller), |
198 | 4.19k | ), |
199 | 4.19k | }; |
200 | 4.19k | } |
201 | | Shared::ExecutingStart => { |
202 | 1 | return Box::new(future::ready(Err( |
203 | 1 | wasmtime::Error::new( |
204 | 1 | NewErr::StartFunctionNotSupported, |
205 | 1 | ), |
206 | 1 | ))); |
207 | | } |
208 | 0 | _ => unreachable!(), |
209 | | } |
210 | | } |
211 | | |
212 | | // Return a future that is ready whenever `Shared` contains |
213 | | // `Return`. |
214 | 4.19k | let shared = shared.clone(); |
215 | 8.39k | Box::new4.19k (future::poll_fn4.19k (move |cx| { |
216 | 8.39k | let mut shared_lock = shared.try_lock().unwrap(); |
217 | 8.39k | match *shared_lock { |
218 | | Shared::EnteredFunctionCall { |
219 | 4.19k | ref mut in_interrupted_waker, |
220 | | .. |
221 | | } |
222 | | | Shared::WithinFunctionCall { |
223 | 0 | ref mut in_interrupted_waker, |
224 | | .. |
225 | | } => { |
226 | 4.19k | *in_interrupted_waker = Some(cx.waker().clone()); |
227 | 4.19k | task::Poll::Pending |
228 | | } |
229 | | Shared::MemoryGrowRequired { |
230 | 6 | ref memory, |
231 | 6 | additional, |
232 | | } => { |
233 | | // The outer call has made sure that `additional` |
234 | | // would fit. |
235 | 6 | memory.grow(&mut caller, additional).unwrap(); |
236 | 6 | *shared_lock = Shared::WithinFunctionCall { |
237 | 6 | in_interrupted_waker: Some(cx.waker().clone()), |
238 | 6 | memory: SliceRawParts( |
239 | 6 | memory.data_ptr(&caller), |
240 | 6 | memory.data_size(&caller), |
241 | 6 | ), |
242 | 6 | expected_return_ty, |
243 | 6 | }; |
244 | 6 | task::Poll::Pending |
245 | | } |
246 | | Shared::Return { |
247 | 4.19k | ref mut return_value, |
248 | 4.19k | memory, |
249 | | } => { |
250 | 4.19k | if let Some(returned2.10k ) = return_value.take() { |
251 | 2.10k | assert_eq!(ret_val.len(), 1); |
252 | 2.10k | ret_val[0] = From::from(returned); |
253 | | } else { |
254 | 2.08k | assert!(ret_val.is_empty()); |
255 | | } |
256 | | |
257 | 4.19k | *shared_lock = Shared::OutsideFunctionCall { memory }; |
258 | 4.19k | task::Poll::Ready(Ok(())) |
259 | | } |
260 | 0 | _ => unreachable!(), |
261 | | } |
262 | 8.39k | })) _RNCNCNvMNtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB6_12JitPrototype20from_base_componentss_00Bc_ Line | Count | Source | 215 | 85 | Box::new(future::poll_fn(move |cx| { | 216 | 85 | let mut shared_lock = shared.try_lock().unwrap(); | 217 | 85 | match *shared_lock { | 218 | | Shared::EnteredFunctionCall { | 219 | 44 | ref mut in_interrupted_waker, | 220 | | .. | 221 | | } | 222 | | | Shared::WithinFunctionCall { | 223 | 0 | ref mut in_interrupted_waker, | 224 | | .. | 225 | | } => { | 226 | 44 | *in_interrupted_waker = Some(cx.waker().clone()); | 227 | 44 | task::Poll::Pending | 228 | | } | 229 | | Shared::MemoryGrowRequired { | 230 | 0 | ref memory, | 231 | 0 | additional, | 232 | | } => { | 233 | | // The outer call has made sure that `additional` | 234 | | // would fit. | 235 | 0 | memory.grow(&mut caller, additional).unwrap(); | 236 | 0 | *shared_lock = Shared::WithinFunctionCall { | 237 | 0 | in_interrupted_waker: Some(cx.waker().clone()), | 238 | 0 | memory: SliceRawParts( | 239 | 0 | memory.data_ptr(&caller), | 240 | 0 | memory.data_size(&caller), | 241 | 0 | ), | 242 | 0 | expected_return_ty, | 243 | 0 | }; | 244 | 0 | task::Poll::Pending | 245 | | } | 246 | | Shared::Return { | 247 | 41 | ref mut return_value, | 248 | 41 | memory, | 249 | | } => { | 250 | 41 | if let Some(returned27 ) = return_value.take() { | 251 | 27 | assert_eq!(ret_val.len(), 1); | 252 | 27 | ret_val[0] = From::from(returned); | 253 | | } else { | 254 | 14 | assert!(ret_val.is_empty()); | 255 | | } | 256 | | | 257 | 41 | *shared_lock = Shared::OutsideFunctionCall { memory }; | 258 | 41 | task::Poll::Ready(Ok(())) | 259 | | } | 260 | 0 | _ => unreachable!(), | 261 | | } | 262 | 85 | })) |
_RNCNCNvMNtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB6_12JitPrototype20from_base_componentss_00Bc_ Line | Count | Source | 215 | 8.31k | Box::new(future::poll_fn(move |cx| { | 216 | 8.31k | let mut shared_lock = shared.try_lock().unwrap(); | 217 | 8.31k | match *shared_lock { | 218 | | Shared::EnteredFunctionCall { | 219 | 4.15k | ref mut in_interrupted_waker, | 220 | | .. | 221 | | } | 222 | | | Shared::WithinFunctionCall { | 223 | 0 | ref mut in_interrupted_waker, | 224 | | .. | 225 | | } => { | 226 | 4.15k | *in_interrupted_waker = Some(cx.waker().clone()); | 227 | 4.15k | task::Poll::Pending | 228 | | } | 229 | | Shared::MemoryGrowRequired { | 230 | 6 | ref memory, | 231 | 6 | additional, | 232 | | } => { | 233 | | // The outer call has made sure that `additional` | 234 | | // would fit. | 235 | 6 | memory.grow(&mut caller, additional).unwrap(); | 236 | 6 | *shared_lock = Shared::WithinFunctionCall { | 237 | 6 | in_interrupted_waker: Some(cx.waker().clone()), | 238 | 6 | memory: SliceRawParts( | 239 | 6 | memory.data_ptr(&caller), | 240 | 6 | memory.data_size(&caller), | 241 | 6 | ), | 242 | 6 | expected_return_ty, | 243 | 6 | }; | 244 | 6 | task::Poll::Pending | 245 | | } | 246 | | Shared::Return { | 247 | 4.15k | ref mut return_value, | 248 | 4.15k | memory, | 249 | | } => { | 250 | 4.15k | if let Some(returned2.07k ) = return_value.take() { | 251 | 2.07k | assert_eq!(ret_val.len(), 1); | 252 | 2.07k | ret_val[0] = From::from(returned); | 253 | | } else { | 254 | 2.07k | assert!(ret_val.is_empty()); | 255 | | } | 256 | | | 257 | 4.15k | *shared_lock = Shared::OutsideFunctionCall { memory }; | 258 | 4.15k | task::Poll::Ready(Ok(())) | 259 | | } | 260 | 0 | _ => unreachable!(), | 261 | | } | 262 | 8.31k | })) |
|
263 | 4.19k | }, _RNCNvMNtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB4_12JitPrototype20from_base_componentss_0Ba_ Line | Count | Source | 170 | 45 | move |mut caller, params, ret_val| { | 171 | | // This closure is executed whenever the Wasm VM calls a | 172 | | // host function. | 173 | | // While a function call is in progress, only this closure can | 174 | | // have access to the `wasmtime::Store`. For this reason, we use | 175 | | // a small communication protocol with the outside. | 176 | | | 177 | | // Transition `shared` from `OutsideFunctionCall` to | 178 | | // `EnteredFunctionCall`. | 179 | | { | 180 | 45 | let mut shared_lock = shared.try_lock().unwrap(); | 181 | 45 | match mem::replace(&mut *shared_lock, Shared::Poisoned) { | 182 | 44 | Shared::OutsideFunctionCall { memory } => { | 183 | 44 | *shared_lock = Shared::EnteredFunctionCall { | 184 | 44 | function_index, | 185 | 44 | // Because the function signature has been | 186 | 44 | // validated at initialization, we can safely | 187 | 44 | // convert all the parameter types. | 188 | 44 | parameters: params | 189 | 44 | .iter() | 190 | 44 | .map(TryFrom::try_from) | 191 | 44 | .collect::<Result<_, _>>() | 192 | 44 | .unwrap(), | 193 | 44 | expected_return_ty, | 194 | 44 | in_interrupted_waker: None, // Filled below | 195 | 44 | memory: SliceRawParts( | 196 | 44 | memory.data_ptr(&caller), | 197 | 44 | memory.data_size(&caller), | 198 | 44 | ), | 199 | 44 | }; | 200 | 44 | } | 201 | | Shared::ExecutingStart => { | 202 | 1 | return Box::new(future::ready(Err( | 203 | 1 | wasmtime::Error::new( | 204 | 1 | NewErr::StartFunctionNotSupported, | 205 | 1 | ), | 206 | 1 | ))); | 207 | | } | 208 | 0 | _ => unreachable!(), | 209 | | } | 210 | | } | 211 | | | 212 | | // Return a future that is ready whenever `Shared` contains | 213 | | // `Return`. | 214 | 44 | let shared = shared.clone(); | 215 | 44 | Box::new(future::poll_fn(move |cx| { | 216 | | let mut shared_lock = shared.try_lock().unwrap(); | 217 | | match *shared_lock { | 218 | | Shared::EnteredFunctionCall { | 219 | | ref mut in_interrupted_waker, | 220 | | .. | 221 | | } | 222 | | | Shared::WithinFunctionCall { | 223 | | ref mut in_interrupted_waker, | 224 | | .. | 225 | | } => { | 226 | | *in_interrupted_waker = Some(cx.waker().clone()); | 227 | | task::Poll::Pending | 228 | | } | 229 | | Shared::MemoryGrowRequired { | 230 | | ref memory, | 231 | | additional, | 232 | | } => { | 233 | | // The outer call has made sure that `additional` | 234 | | // would fit. | 235 | | memory.grow(&mut caller, additional).unwrap(); | 236 | | *shared_lock = Shared::WithinFunctionCall { | 237 | | in_interrupted_waker: Some(cx.waker().clone()), | 238 | | memory: SliceRawParts( | 239 | | memory.data_ptr(&caller), | 240 | | memory.data_size(&caller), | 241 | | ), | 242 | | expected_return_ty, | 243 | | }; | 244 | | task::Poll::Pending | 245 | | } | 246 | | Shared::Return { | 247 | | ref mut return_value, | 248 | | memory, | 249 | | } => { | 250 | | if let Some(returned) = return_value.take() { | 251 | | assert_eq!(ret_val.len(), 1); | 252 | | ret_val[0] = From::from(returned); | 253 | | } else { | 254 | | assert!(ret_val.is_empty()); | 255 | | } | 256 | | | 257 | | *shared_lock = Shared::OutsideFunctionCall { memory }; | 258 | | task::Poll::Ready(Ok(())) | 259 | | } | 260 | | _ => unreachable!(), | 261 | | } | 262 | | })) | 263 | 45 | }, |
_RNCNvMNtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB4_12JitPrototype20from_base_componentss_0Ba_ Line | Count | Source | 170 | 4.15k | move |mut caller, params, ret_val| { | 171 | | // This closure is executed whenever the Wasm VM calls a | 172 | | // host function. | 173 | | // While a function call is in progress, only this closure can | 174 | | // have access to the `wasmtime::Store`. For this reason, we use | 175 | | // a small communication protocol with the outside. | 176 | | | 177 | | // Transition `shared` from `OutsideFunctionCall` to | 178 | | // `EnteredFunctionCall`. | 179 | | { | 180 | 4.15k | let mut shared_lock = shared.try_lock().unwrap(); | 181 | 4.15k | match mem::replace(&mut *shared_lock, Shared::Poisoned) { | 182 | 4.15k | Shared::OutsideFunctionCall { memory } => { | 183 | 4.15k | *shared_lock = Shared::EnteredFunctionCall { | 184 | 4.15k | function_index, | 185 | 4.15k | // Because the function signature has been | 186 | 4.15k | // validated at initialization, we can safely | 187 | 4.15k | // convert all the parameter types. | 188 | 4.15k | parameters: params | 189 | 4.15k | .iter() | 190 | 4.15k | .map(TryFrom::try_from) | 191 | 4.15k | .collect::<Result<_, _>>() | 192 | 4.15k | .unwrap(), | 193 | 4.15k | expected_return_ty, | 194 | 4.15k | in_interrupted_waker: None, // Filled below | 195 | 4.15k | memory: SliceRawParts( | 196 | 4.15k | memory.data_ptr(&caller), | 197 | 4.15k | memory.data_size(&caller), | 198 | 4.15k | ), | 199 | 4.15k | }; | 200 | 4.15k | } | 201 | | Shared::ExecutingStart => { | 202 | 0 | return Box::new(future::ready(Err( | 203 | 0 | wasmtime::Error::new( | 204 | 0 | NewErr::StartFunctionNotSupported, | 205 | 0 | ), | 206 | 0 | ))); | 207 | | } | 208 | 0 | _ => unreachable!(), | 209 | | } | 210 | | } | 211 | | | 212 | | // Return a future that is ready whenever `Shared` contains | 213 | | // `Return`. | 214 | 4.15k | let shared = shared.clone(); | 215 | 4.15k | Box::new(future::poll_fn(move |cx| { | 216 | | let mut shared_lock = shared.try_lock().unwrap(); | 217 | | match *shared_lock { | 218 | | Shared::EnteredFunctionCall { | 219 | | ref mut in_interrupted_waker, | 220 | | .. | 221 | | } | 222 | | | Shared::WithinFunctionCall { | 223 | | ref mut in_interrupted_waker, | 224 | | .. | 225 | | } => { | 226 | | *in_interrupted_waker = Some(cx.waker().clone()); | 227 | | task::Poll::Pending | 228 | | } | 229 | | Shared::MemoryGrowRequired { | 230 | | ref memory, | 231 | | additional, | 232 | | } => { | 233 | | // The outer call has made sure that `additional` | 234 | | // would fit. | 235 | | memory.grow(&mut caller, additional).unwrap(); | 236 | | *shared_lock = Shared::WithinFunctionCall { | 237 | | in_interrupted_waker: Some(cx.waker().clone()), | 238 | | memory: SliceRawParts( | 239 | | memory.data_ptr(&caller), | 240 | | memory.data_size(&caller), | 241 | | ), | 242 | | expected_return_ty, | 243 | | }; | 244 | | task::Poll::Pending | 245 | | } | 246 | | Shared::Return { | 247 | | ref mut return_value, | 248 | | memory, | 249 | | } => { | 250 | | if let Some(returned) = return_value.take() { | 251 | | assert_eq!(ret_val.len(), 1); | 252 | | ret_val[0] = From::from(returned); | 253 | | } else { | 254 | | assert!(ret_val.is_empty()); | 255 | | } | 256 | | | 257 | | *shared_lock = Shared::OutsideFunctionCall { memory }; | 258 | | task::Poll::Ready(Ok(())) | 259 | | } | 260 | | _ => unreachable!(), | 261 | | } | 262 | | })) | 263 | 4.15k | }, |
|
264 | | ))); |
265 | | } |
266 | | wasmtime::ExternType::Global(_) | wasmtime::ExternType::Table(_) => { |
267 | 0 | unreachable!() // Should have been checked earlier. |
268 | | } |
269 | 59 | wasmtime::ExternType::Memory(m) => { |
270 | 59 | if module_import.module() != "env" || module_import.name() != "memory" { |
271 | 1 | return Err(NewErr::MemoryNotNamedMemory); |
272 | 58 | } |
273 | | |
274 | | // Considering that the memory can only be "env":"memory", and that each |
275 | | // import has a unique name, this block can't be reached more than once. |
276 | 58 | debug_assert!(imported_memory.is_none()); |
277 | | imported_memory = Some( |
278 | 58 | wasmtime::Memory::new(&mut store, m) |
279 | 58 | .map_err(|_| NewErr::CouldntAllocateMemory)?0 , |
280 | | ); |
281 | 58 | imports.push(wasmtime::Extern::Memory(*imported_memory.as_ref().unwrap())); |
282 | | } |
283 | | }; |
284 | | } |
285 | 84 | imports |
286 | | }; |
287 | | |
288 | | // Calling `wasmtime::Instance::new` executes the `start` function of the module, if any. |
289 | | // If this `start` function calls into one of the imports, then the import will detect |
290 | | // that the shared state is `ExecutingStart` and return an error. |
291 | | // This function call is asynchronous because the `start` function might be asynchronous. |
292 | | // In principle, `now_or_never()` can be unwrapped because the only way for `start` to |
293 | | // not be immediately finished is if it enters an import, which immediately returns an |
294 | | // error. However we return an error anyway, just in case. |
295 | | // If the `start` function doesn't call any import, then it will go undetected and no |
296 | | // error will be returned. |
297 | | // TODO: detect `start` anyway, for consistency with other backends |
298 | 84 | let instance82 = match Future::poll( |
299 | 84 | pin::pin!(wasmtime::Instance::new_async( |
300 | 84 | &mut store, |
301 | 84 | &base_components.module, |
302 | 84 | &imports |
303 | | )), |
304 | 84 | &mut task::Context::from_waker(task::Waker::noop()), |
305 | | ) { |
306 | 0 | task::Poll::Pending => return Err(NewErr::StartFunctionNotSupported), // TODO: hacky error value, as the error could also be different |
307 | 82 | task::Poll::Ready(Ok(i)) => i, |
308 | 2 | task::Poll::Ready(Err(err)) => return Err(NewErr::Instantiation(err.to_string())), |
309 | | }; |
310 | | |
311 | | // Now that we are passed the `start` stage, update the state of execution. |
312 | 82 | *shared.lock().unwrap() = Shared::Poisoned; |
313 | | |
314 | 82 | let exported_memory81 = if let Some(mem23 ) = instance.get_export(&mut store, "memory") { |
315 | 23 | if let Some(mem22 ) = mem.into_memory() { |
316 | 22 | Some(mem) |
317 | | } else { |
318 | 1 | return Err(NewErr::MemoryIsntMemory); |
319 | | } |
320 | | } else { |
321 | 59 | None |
322 | | }; |
323 | | |
324 | 81 | let memory80 = match (exported_memory, imported_memory) { |
325 | 0 | (Some(_), Some(_)) => return Err(NewErr::TwoMemories), |
326 | 22 | (Some(m), None) => m, |
327 | 58 | (None, Some(m)) => m, |
328 | 1 | (None, None) => return Err(NewErr::NoMemory), |
329 | | }; |
330 | | |
331 | 80 | let memory_type = memory.ty(&store); |
332 | | |
333 | 80 | Ok(JitPrototype { |
334 | 80 | base_components, |
335 | 80 | store, |
336 | 80 | instance, |
337 | 80 | shared, |
338 | 80 | memory, |
339 | 80 | memory_type, |
340 | 80 | }) |
341 | 85 | } _RNvMNtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB2_12JitPrototype20from_base_components Line | Count | Source | 141 | 61 | fn from_base_components(base_components: BaseComponents) -> Result<Self, NewErr> { | 142 | 61 | let mut store = wasmtime::Store::new(base_components.module.engine(), ()); | 143 | | | 144 | 61 | let mut imported_memory = None; | 145 | 61 | let shared = Arc::new(Mutex::new(Shared::ExecutingStart)); | 146 | | | 147 | | // Building the list of symbols that the Wasm VM is able to use. | 148 | 60 | let imports = { | 149 | 61 | let mut imports = Vec::with_capacity(base_components.module.imports().len()); | 150 | 244 | for (module_import, resolved_function) in base_components | 151 | 61 | .module | 152 | 61 | .imports() | 153 | 61 | .zip(base_components.resolved_imports.iter()) | 154 | | { | 155 | 244 | match module_import.ty() { | 156 | 209 | wasmtime::ExternType::Func(func_type) => { | 157 | 209 | let function_index = resolved_function.unwrap(); | 158 | 209 | let shared = shared.clone(); | 159 | | | 160 | | // Obtain `expected_return_ty`. We know that the type is supported due to | 161 | | // the signature check earlier. | 162 | 209 | let expected_return_ty = func_type | 163 | 209 | .results() | 164 | 209 | .next() | 165 | 209 | .map(|v| ValueType::try_from(v).unwrap()); | 166 | | | 167 | 209 | imports.push(wasmtime::Extern::Func(wasmtime::Func::new_async( | 168 | 209 | &mut store, | 169 | 209 | func_type, | 170 | | move |mut caller, params, ret_val| { | 171 | | // This closure is executed whenever the Wasm VM calls a | 172 | | // host function. | 173 | | // While a function call is in progress, only this closure can | 174 | | // have access to the `wasmtime::Store`. For this reason, we use | 175 | | // a small communication protocol with the outside. | 176 | | | 177 | | // Transition `shared` from `OutsideFunctionCall` to | 178 | | // `EnteredFunctionCall`. | 179 | | { | 180 | | let mut shared_lock = shared.try_lock().unwrap(); | 181 | | match mem::replace(&mut *shared_lock, Shared::Poisoned) { | 182 | | Shared::OutsideFunctionCall { memory } => { | 183 | | *shared_lock = Shared::EnteredFunctionCall { | 184 | | function_index, | 185 | | // Because the function signature has been | 186 | | // validated at initialization, we can safely | 187 | | // convert all the parameter types. | 188 | | parameters: params | 189 | | .iter() | 190 | | .map(TryFrom::try_from) | 191 | | .collect::<Result<_, _>>() | 192 | | .unwrap(), | 193 | | expected_return_ty, | 194 | | in_interrupted_waker: None, // Filled below | 195 | | memory: SliceRawParts( | 196 | | memory.data_ptr(&caller), | 197 | | memory.data_size(&caller), | 198 | | ), | 199 | | }; | 200 | | } | 201 | | Shared::ExecutingStart => { | 202 | | return Box::new(future::ready(Err( | 203 | | wasmtime::Error::new( | 204 | | NewErr::StartFunctionNotSupported, | 205 | | ), | 206 | | ))); | 207 | | } | 208 | | _ => unreachable!(), | 209 | | } | 210 | | } | 211 | | | 212 | | // Return a future that is ready whenever `Shared` contains | 213 | | // `Return`. | 214 | | let shared = shared.clone(); | 215 | | Box::new(future::poll_fn(move |cx| { | 216 | | let mut shared_lock = shared.try_lock().unwrap(); | 217 | | match *shared_lock { | 218 | | Shared::EnteredFunctionCall { | 219 | | ref mut in_interrupted_waker, | 220 | | .. | 221 | | } | 222 | | | Shared::WithinFunctionCall { | 223 | | ref mut in_interrupted_waker, | 224 | | .. | 225 | | } => { | 226 | | *in_interrupted_waker = Some(cx.waker().clone()); | 227 | | task::Poll::Pending | 228 | | } | 229 | | Shared::MemoryGrowRequired { | 230 | | ref memory, | 231 | | additional, | 232 | | } => { | 233 | | // The outer call has made sure that `additional` | 234 | | // would fit. | 235 | | memory.grow(&mut caller, additional).unwrap(); | 236 | | *shared_lock = Shared::WithinFunctionCall { | 237 | | in_interrupted_waker: Some(cx.waker().clone()), | 238 | | memory: SliceRawParts( | 239 | | memory.data_ptr(&caller), | 240 | | memory.data_size(&caller), | 241 | | ), | 242 | | expected_return_ty, | 243 | | }; | 244 | | task::Poll::Pending | 245 | | } | 246 | | Shared::Return { | 247 | | ref mut return_value, | 248 | | memory, | 249 | | } => { | 250 | | if let Some(returned) = return_value.take() { | 251 | | assert_eq!(ret_val.len(), 1); | 252 | | ret_val[0] = From::from(returned); | 253 | | } else { | 254 | | assert!(ret_val.is_empty()); | 255 | | } | 256 | | | 257 | | *shared_lock = Shared::OutsideFunctionCall { memory }; | 258 | | task::Poll::Ready(Ok(())) | 259 | | } | 260 | | _ => unreachable!(), | 261 | | } | 262 | | })) | 263 | | }, | 264 | | ))); | 265 | | } | 266 | | wasmtime::ExternType::Global(_) | wasmtime::ExternType::Table(_) => { | 267 | 0 | unreachable!() // Should have been checked earlier. | 268 | | } | 269 | 35 | wasmtime::ExternType::Memory(m) => { | 270 | 35 | if module_import.module() != "env" || module_import.name() != "memory" { | 271 | 1 | return Err(NewErr::MemoryNotNamedMemory); | 272 | 34 | } | 273 | | | 274 | | // Considering that the memory can only be "env":"memory", and that each | 275 | | // import has a unique name, this block can't be reached more than once. | 276 | 34 | debug_assert!(imported_memory.is_none()); | 277 | | imported_memory = Some( | 278 | 34 | wasmtime::Memory::new(&mut store, m) | 279 | 34 | .map_err(|_| NewErr::CouldntAllocateMemory)?0 , | 280 | | ); | 281 | 34 | imports.push(wasmtime::Extern::Memory(*imported_memory.as_ref().unwrap())); | 282 | | } | 283 | | }; | 284 | | } | 285 | 60 | imports | 286 | | }; | 287 | | | 288 | | // Calling `wasmtime::Instance::new` executes the `start` function of the module, if any. | 289 | | // If this `start` function calls into one of the imports, then the import will detect | 290 | | // that the shared state is `ExecutingStart` and return an error. | 291 | | // This function call is asynchronous because the `start` function might be asynchronous. | 292 | | // In principle, `now_or_never()` can be unwrapped because the only way for `start` to | 293 | | // not be immediately finished is if it enters an import, which immediately returns an | 294 | | // error. However we return an error anyway, just in case. | 295 | | // If the `start` function doesn't call any import, then it will go undetected and no | 296 | | // error will be returned. | 297 | | // TODO: detect `start` anyway, for consistency with other backends | 298 | 60 | let instance58 = match Future::poll( | 299 | 60 | pin::pin!(wasmtime::Instance::new_async( | 300 | 60 | &mut store, | 301 | 60 | &base_components.module, | 302 | 60 | &imports | 303 | | )), | 304 | 60 | &mut task::Context::from_waker(task::Waker::noop()), | 305 | | ) { | 306 | 0 | task::Poll::Pending => return Err(NewErr::StartFunctionNotSupported), // TODO: hacky error value, as the error could also be different | 307 | 58 | task::Poll::Ready(Ok(i)) => i, | 308 | 2 | task::Poll::Ready(Err(err)) => return Err(NewErr::Instantiation(err.to_string())), | 309 | | }; | 310 | | | 311 | | // Now that we are passed the `start` stage, update the state of execution. | 312 | 58 | *shared.lock().unwrap() = Shared::Poisoned; | 313 | | | 314 | 58 | let exported_memory57 = if let Some(mem23 ) = instance.get_export(&mut store, "memory") { | 315 | 23 | if let Some(mem22 ) = mem.into_memory() { | 316 | 22 | Some(mem) | 317 | | } else { | 318 | 1 | return Err(NewErr::MemoryIsntMemory); | 319 | | } | 320 | | } else { | 321 | 35 | None | 322 | | }; | 323 | | | 324 | 57 | let memory56 = match (exported_memory, imported_memory) { | 325 | 0 | (Some(_), Some(_)) => return Err(NewErr::TwoMemories), | 326 | 22 | (Some(m), None) => m, | 327 | 34 | (None, Some(m)) => m, | 328 | 1 | (None, None) => return Err(NewErr::NoMemory), | 329 | | }; | 330 | | | 331 | 56 | let memory_type = memory.ty(&store); | 332 | | | 333 | 56 | Ok(JitPrototype { | 334 | 56 | base_components, | 335 | 56 | store, | 336 | 56 | instance, | 337 | 56 | shared, | 338 | 56 | memory, | 339 | 56 | memory_type, | 340 | 56 | }) | 341 | 61 | } |
_RNvMNtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB2_12JitPrototype20from_base_components Line | Count | Source | 141 | 24 | fn from_base_components(base_components: BaseComponents) -> Result<Self, NewErr> { | 142 | 24 | let mut store = wasmtime::Store::new(base_components.module.engine(), ()); | 143 | | | 144 | 24 | let mut imported_memory = None; | 145 | 24 | let shared = Arc::new(Mutex::new(Shared::ExecutingStart)); | 146 | | | 147 | | // Building the list of symbols that the Wasm VM is able to use. | 148 | 24 | let imports = { | 149 | 24 | let mut imports = Vec::with_capacity(base_components.module.imports().len()); | 150 | 720 | for (module_import, resolved_function) in base_components | 151 | 24 | .module | 152 | 24 | .imports() | 153 | 24 | .zip(base_components.resolved_imports.iter()) | 154 | | { | 155 | 720 | match module_import.ty() { | 156 | 696 | wasmtime::ExternType::Func(func_type) => { | 157 | 696 | let function_index = resolved_function.unwrap(); | 158 | 696 | let shared = shared.clone(); | 159 | | | 160 | | // Obtain `expected_return_ty`. We know that the type is supported due to | 161 | | // the signature check earlier. | 162 | 696 | let expected_return_ty = func_type | 163 | 696 | .results() | 164 | 696 | .next() | 165 | 696 | .map(|v| ValueType::try_from(v).unwrap()); | 166 | | | 167 | 696 | imports.push(wasmtime::Extern::Func(wasmtime::Func::new_async( | 168 | 696 | &mut store, | 169 | 696 | func_type, | 170 | | move |mut caller, params, ret_val| { | 171 | | // This closure is executed whenever the Wasm VM calls a | 172 | | // host function. | 173 | | // While a function call is in progress, only this closure can | 174 | | // have access to the `wasmtime::Store`. For this reason, we use | 175 | | // a small communication protocol with the outside. | 176 | | | 177 | | // Transition `shared` from `OutsideFunctionCall` to | 178 | | // `EnteredFunctionCall`. | 179 | | { | 180 | | let mut shared_lock = shared.try_lock().unwrap(); | 181 | | match mem::replace(&mut *shared_lock, Shared::Poisoned) { | 182 | | Shared::OutsideFunctionCall { memory } => { | 183 | | *shared_lock = Shared::EnteredFunctionCall { | 184 | | function_index, | 185 | | // Because the function signature has been | 186 | | // validated at initialization, we can safely | 187 | | // convert all the parameter types. | 188 | | parameters: params | 189 | | .iter() | 190 | | .map(TryFrom::try_from) | 191 | | .collect::<Result<_, _>>() | 192 | | .unwrap(), | 193 | | expected_return_ty, | 194 | | in_interrupted_waker: None, // Filled below | 195 | | memory: SliceRawParts( | 196 | | memory.data_ptr(&caller), | 197 | | memory.data_size(&caller), | 198 | | ), | 199 | | }; | 200 | | } | 201 | | Shared::ExecutingStart => { | 202 | | return Box::new(future::ready(Err( | 203 | | wasmtime::Error::new( | 204 | | NewErr::StartFunctionNotSupported, | 205 | | ), | 206 | | ))); | 207 | | } | 208 | | _ => unreachable!(), | 209 | | } | 210 | | } | 211 | | | 212 | | // Return a future that is ready whenever `Shared` contains | 213 | | // `Return`. | 214 | | let shared = shared.clone(); | 215 | | Box::new(future::poll_fn(move |cx| { | 216 | | let mut shared_lock = shared.try_lock().unwrap(); | 217 | | match *shared_lock { | 218 | | Shared::EnteredFunctionCall { | 219 | | ref mut in_interrupted_waker, | 220 | | .. | 221 | | } | 222 | | | Shared::WithinFunctionCall { | 223 | | ref mut in_interrupted_waker, | 224 | | .. | 225 | | } => { | 226 | | *in_interrupted_waker = Some(cx.waker().clone()); | 227 | | task::Poll::Pending | 228 | | } | 229 | | Shared::MemoryGrowRequired { | 230 | | ref memory, | 231 | | additional, | 232 | | } => { | 233 | | // The outer call has made sure that `additional` | 234 | | // would fit. | 235 | | memory.grow(&mut caller, additional).unwrap(); | 236 | | *shared_lock = Shared::WithinFunctionCall { | 237 | | in_interrupted_waker: Some(cx.waker().clone()), | 238 | | memory: SliceRawParts( | 239 | | memory.data_ptr(&caller), | 240 | | memory.data_size(&caller), | 241 | | ), | 242 | | expected_return_ty, | 243 | | }; | 244 | | task::Poll::Pending | 245 | | } | 246 | | Shared::Return { | 247 | | ref mut return_value, | 248 | | memory, | 249 | | } => { | 250 | | if let Some(returned) = return_value.take() { | 251 | | assert_eq!(ret_val.len(), 1); | 252 | | ret_val[0] = From::from(returned); | 253 | | } else { | 254 | | assert!(ret_val.is_empty()); | 255 | | } | 256 | | | 257 | | *shared_lock = Shared::OutsideFunctionCall { memory }; | 258 | | task::Poll::Ready(Ok(())) | 259 | | } | 260 | | _ => unreachable!(), | 261 | | } | 262 | | })) | 263 | | }, | 264 | | ))); | 265 | | } | 266 | | wasmtime::ExternType::Global(_) | wasmtime::ExternType::Table(_) => { | 267 | 0 | unreachable!() // Should have been checked earlier. | 268 | | } | 269 | 24 | wasmtime::ExternType::Memory(m) => { | 270 | 24 | if module_import.module() != "env" || module_import.name() != "memory" { | 271 | 0 | return Err(NewErr::MemoryNotNamedMemory); | 272 | 24 | } | 273 | | | 274 | | // Considering that the memory can only be "env":"memory", and that each | 275 | | // import has a unique name, this block can't be reached more than once. | 276 | 24 | debug_assert!(imported_memory.is_none()); | 277 | | imported_memory = Some( | 278 | 24 | wasmtime::Memory::new(&mut store, m) | 279 | 24 | .map_err(|_| NewErr::CouldntAllocateMemory)?0 , | 280 | | ); | 281 | 24 | imports.push(wasmtime::Extern::Memory(*imported_memory.as_ref().unwrap())); | 282 | | } | 283 | | }; | 284 | | } | 285 | 24 | imports | 286 | | }; | 287 | | | 288 | | // Calling `wasmtime::Instance::new` executes the `start` function of the module, if any. | 289 | | // If this `start` function calls into one of the imports, then the import will detect | 290 | | // that the shared state is `ExecutingStart` and return an error. | 291 | | // This function call is asynchronous because the `start` function might be asynchronous. | 292 | | // In principle, `now_or_never()` can be unwrapped because the only way for `start` to | 293 | | // not be immediately finished is if it enters an import, which immediately returns an | 294 | | // error. However we return an error anyway, just in case. | 295 | | // If the `start` function doesn't call any import, then it will go undetected and no | 296 | | // error will be returned. | 297 | | // TODO: detect `start` anyway, for consistency with other backends | 298 | 24 | let instance = match Future::poll( | 299 | 24 | pin::pin!(wasmtime::Instance::new_async( | 300 | 24 | &mut store, | 301 | 24 | &base_components.module, | 302 | 24 | &imports | 303 | | )), | 304 | 24 | &mut task::Context::from_waker(task::Waker::noop()), | 305 | | ) { | 306 | 0 | task::Poll::Pending => return Err(NewErr::StartFunctionNotSupported), // TODO: hacky error value, as the error could also be different | 307 | 24 | task::Poll::Ready(Ok(i)) => i, | 308 | 0 | task::Poll::Ready(Err(err)) => return Err(NewErr::Instantiation(err.to_string())), | 309 | | }; | 310 | | | 311 | | // Now that we are passed the `start` stage, update the state of execution. | 312 | 24 | *shared.lock().unwrap() = Shared::Poisoned; | 313 | | | 314 | 24 | let exported_memory = if let Some(mem0 ) = instance.get_export(&mut store, "memory") { | 315 | 0 | if let Some(mem) = mem.into_memory() { | 316 | 0 | Some(mem) | 317 | | } else { | 318 | 0 | return Err(NewErr::MemoryIsntMemory); | 319 | | } | 320 | | } else { | 321 | 24 | None | 322 | | }; | 323 | | | 324 | 24 | let memory = match (exported_memory, imported_memory) { | 325 | 0 | (Some(_), Some(_)) => return Err(NewErr::TwoMemories), | 326 | 0 | (Some(m), None) => m, | 327 | 24 | (None, Some(m)) => m, | 328 | 0 | (None, None) => return Err(NewErr::NoMemory), | 329 | | }; | 330 | | | 331 | 24 | let memory_type = memory.ty(&store); | 332 | | | 333 | 24 | Ok(JitPrototype { | 334 | 24 | base_components, | 335 | 24 | store, | 336 | 24 | instance, | 337 | 24 | shared, | 338 | 24 | memory, | 339 | 24 | memory_type, | 340 | 24 | }) | 341 | 24 | } |
|
342 | | |
343 | | /// See [`super::VirtualMachinePrototype::global_value`]. |
344 | 54 | pub fn global_value(&mut self, name: &str) -> Result<u32, GlobalValueErr> { |
345 | 54 | match self.instance.get_export(&mut self.store, name) { |
346 | 51 | Some(wasmtime::Extern::Global(g)) => match g.get(&mut self.store) { |
347 | 50 | wasmtime::Val::I32(v) => Ok(u32::from_ne_bytes(v.to_ne_bytes())), |
348 | 1 | _ => Err(GlobalValueErr::Invalid), |
349 | | }, |
350 | 3 | _ => Err(GlobalValueErr::NotFound), |
351 | | } |
352 | 54 | } _RNvMNtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB2_12JitPrototype12global_value Line | Count | Source | 344 | 31 | pub fn global_value(&mut self, name: &str) -> Result<u32, GlobalValueErr> { | 345 | 31 | match self.instance.get_export(&mut self.store, name) { | 346 | 28 | Some(wasmtime::Extern::Global(g)) => match g.get(&mut self.store) { | 347 | 27 | wasmtime::Val::I32(v) => Ok(u32::from_ne_bytes(v.to_ne_bytes())), | 348 | 1 | _ => Err(GlobalValueErr::Invalid), | 349 | | }, | 350 | 3 | _ => Err(GlobalValueErr::NotFound), | 351 | | } | 352 | 31 | } |
_RNvMNtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB2_12JitPrototype12global_value Line | Count | Source | 344 | 23 | pub fn global_value(&mut self, name: &str) -> Result<u32, GlobalValueErr> { | 345 | 23 | match self.instance.get_export(&mut self.store, name) { | 346 | 23 | Some(wasmtime::Extern::Global(g)) => match g.get(&mut self.store) { | 347 | 23 | wasmtime::Val::I32(v) => Ok(u32::from_ne_bytes(v.to_ne_bytes())), | 348 | 0 | _ => Err(GlobalValueErr::Invalid), | 349 | | }, | 350 | 0 | _ => Err(GlobalValueErr::NotFound), | 351 | | } | 352 | 23 | } |
|
353 | | |
354 | | /// See [`super::VirtualMachinePrototype::memory_max_pages`]. |
355 | 49 | pub fn memory_max_pages(&self) -> Option<HeapPages> { |
356 | 49 | let num2 = self.memory.ty(&self.store).maximum()?47 ; |
357 | 2 | match u32::try_from(num) { |
358 | 2 | Ok(n) => Some(HeapPages::new(n)), |
359 | | // If `num` doesn't fit in a `u32`, we return `None` to mean "infinite". |
360 | 0 | Err(_) => None, |
361 | | } |
362 | 49 | } _RNvMNtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB2_12JitPrototype16memory_max_pages Line | Count | Source | 355 | 26 | pub fn memory_max_pages(&self) -> Option<HeapPages> { | 356 | 26 | let num2 = self.memory.ty(&self.store).maximum()?24 ; | 357 | 2 | match u32::try_from(num) { | 358 | 2 | Ok(n) => Some(HeapPages::new(n)), | 359 | | // If `num` doesn't fit in a `u32`, we return `None` to mean "infinite". | 360 | 0 | Err(_) => None, | 361 | | } | 362 | 26 | } |
_RNvMNtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB2_12JitPrototype16memory_max_pages Line | Count | Source | 355 | 23 | pub fn memory_max_pages(&self) -> Option<HeapPages> { | 356 | 23 | let num0 = self.memory.ty(&self.store).maximum()?; | 357 | 0 | match u32::try_from(num) { | 358 | 0 | Ok(n) => Some(HeapPages::new(n)), | 359 | | // If `num` doesn't fit in a `u32`, we return `None` to mean "infinite". | 360 | 0 | Err(_) => None, | 361 | | } | 362 | 23 | } |
|
363 | | |
364 | | /// See [`super::VirtualMachinePrototype::prepare`]. |
365 | 37 | pub fn prepare(self) -> Prepare { |
366 | 37 | Prepare { inner: self } |
367 | 37 | } _RNvMNtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB2_12JitPrototype7prepare Line | Count | Source | 365 | 36 | pub fn prepare(self) -> Prepare { | 366 | 36 | Prepare { inner: self } | 367 | 36 | } |
_RNvMNtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB2_12JitPrototype7prepare Line | Count | Source | 365 | 1 | pub fn prepare(self) -> Prepare { | 366 | 1 | Prepare { inner: self } | 367 | 1 | } |
|
368 | | } |
369 | | |
370 | | impl Clone for JitPrototype { |
371 | 1 | fn clone(&self) -> Self { |
372 | | // `from_base_components` is deterministic: either it errors all the time or it never |
373 | | // errors. Since we've called it before and it didn't error, we know that it will also |
374 | | // not error. |
375 | | // The only exception is `NewErr::CouldntAllocateMemory`, but lack of memory is always an |
376 | | // acceptable reason to panic. |
377 | 1 | JitPrototype::from_base_components(BaseComponents { |
378 | 1 | module: self.base_components.module.clone(), |
379 | 1 | resolved_imports: self.base_components.resolved_imports.clone(), |
380 | 1 | }) |
381 | 1 | .unwrap() |
382 | 1 | } Unexecuted instantiation: _RNvXs_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB4_12JitPrototypeNtNtCs1p5UDGgVI4d_4core5clone5Clone5clone _RNvXs_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB4_12JitPrototypeNtNtCs1p5UDGgVI4d_4core5clone5Clone5clone Line | Count | Source | 371 | 1 | fn clone(&self) -> Self { | 372 | | // `from_base_components` is deterministic: either it errors all the time or it never | 373 | | // errors. Since we've called it before and it didn't error, we know that it will also | 374 | | // not error. | 375 | | // The only exception is `NewErr::CouldntAllocateMemory`, but lack of memory is always an | 376 | | // acceptable reason to panic. | 377 | 1 | JitPrototype::from_base_components(BaseComponents { | 378 | 1 | module: self.base_components.module.clone(), | 379 | 1 | resolved_imports: self.base_components.resolved_imports.clone(), | 380 | 1 | }) | 381 | 1 | .unwrap() | 382 | 1 | } |
|
383 | | } |
384 | | |
385 | | impl fmt::Debug for JitPrototype { |
386 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
387 | 0 | f.debug_tuple("JitPrototype").finish() |
388 | 0 | } Unexecuted instantiation: _RNvXs0_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB5_12JitPrototypeNtNtCs1p5UDGgVI4d_4core3fmt5Debug3fmt Unexecuted instantiation: _RNvXs0_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB5_12JitPrototypeNtNtCs1p5UDGgVI4d_4core3fmt5Debug3fmt |
389 | | } |
390 | | |
391 | | /// See [`super::Prepare`]. |
392 | | pub struct Prepare { |
393 | | inner: JitPrototype, |
394 | | } |
395 | | |
396 | | impl Prepare { |
397 | | /// See [`super::Prepare::into_prototype`]. |
398 | 1 | pub fn into_prototype(self) -> JitPrototype { |
399 | | // Since the creation has succeeded before, there's no reason why it would fail now. |
400 | 1 | JitPrototype::from_base_components(self.inner.base_components).unwrap() |
401 | 1 | } _RNvMs1_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB5_7Prepare14into_prototype Line | Count | Source | 398 | 1 | pub fn into_prototype(self) -> JitPrototype { | 399 | | // Since the creation has succeeded before, there's no reason why it would fail now. | 400 | 1 | JitPrototype::from_base_components(self.inner.base_components).unwrap() | 401 | 1 | } |
Unexecuted instantiation: _RNvMs1_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB5_7Prepare14into_prototype |
402 | | |
403 | | /// See [`super::Prepare::memory_size`]. |
404 | 42 | pub fn memory_size(&self) -> HeapPages { |
405 | 42 | let heap_pages = self.inner.memory.size(&self.inner.store); |
406 | 42 | HeapPages::new(u32::try_from(heap_pages).unwrap()) |
407 | 42 | } _RNvMs1_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB5_7Prepare11memory_size Line | Count | Source | 404 | 40 | pub fn memory_size(&self) -> HeapPages { | 405 | 40 | let heap_pages = self.inner.memory.size(&self.inner.store); | 406 | 40 | HeapPages::new(u32::try_from(heap_pages).unwrap()) | 407 | 40 | } |
_RNvMs1_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB5_7Prepare11memory_size Line | Count | Source | 404 | 2 | pub fn memory_size(&self) -> HeapPages { | 405 | 2 | let heap_pages = self.inner.memory.size(&self.inner.store); | 406 | 2 | HeapPages::new(u32::try_from(heap_pages).unwrap()) | 407 | 2 | } |
|
408 | | |
409 | | /// See [`super::Prepare::read_memory`]. |
410 | 4 | pub fn read_memory( |
411 | 4 | &self, |
412 | 4 | offset: u32, |
413 | 4 | size: u32, |
414 | 4 | ) -> Result<impl AsRef<[u8]>, OutOfBoundsError> { |
415 | 4 | let memory_slice = self.inner.memory.data(&self.inner.store); |
416 | | |
417 | 4 | let start = usize::try_from(offset).map_err(|_| OutOfBoundsError)?0 ; |
418 | 4 | let end = start |
419 | 4 | .checked_add(usize::try_from(size).map_err(|_| OutOfBoundsError)?0 ) |
420 | 4 | .ok_or(OutOfBoundsError)?0 ; |
421 | | |
422 | 4 | if end > memory_slice.len() { |
423 | 0 | return Err(OutOfBoundsError); |
424 | 4 | } |
425 | | |
426 | 4 | Ok(&memory_slice[start..end]) |
427 | 4 | } _RNvMs1_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB5_7Prepare11read_memory Line | Count | Source | 410 | 4 | pub fn read_memory( | 411 | 4 | &self, | 412 | 4 | offset: u32, | 413 | 4 | size: u32, | 414 | 4 | ) -> Result<impl AsRef<[u8]>, OutOfBoundsError> { | 415 | 4 | let memory_slice = self.inner.memory.data(&self.inner.store); | 416 | | | 417 | 4 | let start = usize::try_from(offset).map_err(|_| OutOfBoundsError)?0 ; | 418 | 4 | let end = start | 419 | 4 | .checked_add(usize::try_from(size).map_err(|_| OutOfBoundsError)?0 ) | 420 | 4 | .ok_or(OutOfBoundsError)?0 ; | 421 | | | 422 | 4 | if end > memory_slice.len() { | 423 | 0 | return Err(OutOfBoundsError); | 424 | 4 | } | 425 | | | 426 | 4 | Ok(&memory_slice[start..end]) | 427 | 4 | } |
Unexecuted instantiation: _RNvMs1_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB5_7Prepare11read_memory |
428 | | |
429 | | /// See [`super::Prepare::write_memory`]. |
430 | 40 | pub fn write_memory(&mut self, offset: u32, value: &[u8]) -> Result<(), OutOfBoundsError> { |
431 | 40 | let memory_slice = self.inner.memory.data_mut(&mut self.inner.store); |
432 | | |
433 | 40 | let start = usize::try_from(offset).map_err(|_| OutOfBoundsError)?0 ; |
434 | 40 | let end = start.checked_add(value.len()).ok_or(OutOfBoundsError)?0 ; |
435 | | |
436 | 40 | if end > memory_slice.len() { |
437 | 0 | return Err(OutOfBoundsError); |
438 | 40 | } |
439 | | |
440 | 40 | if !value.is_empty() { |
441 | 35 | memory_slice[start..end].copy_from_slice(value); |
442 | 35 | }5 |
443 | | |
444 | 40 | Ok(()) |
445 | 40 | } _RNvMs1_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB5_7Prepare12write_memory Line | Count | Source | 430 | 39 | pub fn write_memory(&mut self, offset: u32, value: &[u8]) -> Result<(), OutOfBoundsError> { | 431 | 39 | let memory_slice = self.inner.memory.data_mut(&mut self.inner.store); | 432 | | | 433 | 39 | let start = usize::try_from(offset).map_err(|_| OutOfBoundsError)?0 ; | 434 | 39 | let end = start.checked_add(value.len()).ok_or(OutOfBoundsError)?0 ; | 435 | | | 436 | 39 | if end > memory_slice.len() { | 437 | 0 | return Err(OutOfBoundsError); | 438 | 39 | } | 439 | | | 440 | 39 | if !value.is_empty() { | 441 | 34 | memory_slice[start..end].copy_from_slice(value); | 442 | 34 | }5 | 443 | | | 444 | 39 | Ok(()) | 445 | 39 | } |
_RNvMs1_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB5_7Prepare12write_memory Line | Count | Source | 430 | 1 | pub fn write_memory(&mut self, offset: u32, value: &[u8]) -> Result<(), OutOfBoundsError> { | 431 | 1 | let memory_slice = self.inner.memory.data_mut(&mut self.inner.store); | 432 | | | 433 | 1 | let start = usize::try_from(offset).map_err(|_| OutOfBoundsError)?0 ; | 434 | 1 | let end = start.checked_add(value.len()).ok_or(OutOfBoundsError)?0 ; | 435 | | | 436 | 1 | if end > memory_slice.len() { | 437 | 0 | return Err(OutOfBoundsError); | 438 | 1 | } | 439 | | | 440 | 1 | if !value.is_empty() { | 441 | 1 | memory_slice[start..end].copy_from_slice(value); | 442 | 1 | }0 | 443 | | | 444 | 1 | Ok(()) | 445 | 1 | } |
|
446 | | |
447 | | /// See [`super::Prepare::grow_memory`]. |
448 | 28 | pub fn grow_memory(&mut self, additional: HeapPages) -> Result<(), OutOfBoundsError> { |
449 | 28 | let additional = u64::from(u32::from(additional)); |
450 | 28 | self.inner |
451 | 28 | .memory |
452 | 28 | .grow(&mut self.inner.store, additional) |
453 | 28 | .map_err(|_| OutOfBoundsError)?0 ; |
454 | 28 | Ok(()) |
455 | 28 | } _RNvMs1_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB5_7Prepare11grow_memory Line | Count | Source | 448 | 27 | pub fn grow_memory(&mut self, additional: HeapPages) -> Result<(), OutOfBoundsError> { | 449 | 27 | let additional = u64::from(u32::from(additional)); | 450 | 27 | self.inner | 451 | 27 | .memory | 452 | 27 | .grow(&mut self.inner.store, additional) | 453 | 27 | .map_err(|_| OutOfBoundsError)?0 ; | 454 | 27 | Ok(()) | 455 | 27 | } |
_RNvMs1_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB5_7Prepare11grow_memory Line | Count | Source | 448 | 1 | pub fn grow_memory(&mut self, additional: HeapPages) -> Result<(), OutOfBoundsError> { | 449 | 1 | let additional = u64::from(u32::from(additional)); | 450 | 1 | self.inner | 451 | 1 | .memory | 452 | 1 | .grow(&mut self.inner.store, additional) | 453 | 1 | .map_err(|_| OutOfBoundsError)?0 ; | 454 | 1 | Ok(()) | 455 | 1 | } |
|
456 | | |
457 | | /// See [`super::Prepare::start`]. |
458 | 35 | pub fn start( |
459 | 35 | mut self, |
460 | 35 | function_name: &str, |
461 | 35 | params: &[WasmValue], |
462 | 35 | ) -> Result<Jit, (StartErr, JitPrototype)> { |
463 | 35 | let function_to_call32 = match self |
464 | 35 | .inner |
465 | 35 | .instance |
466 | 35 | .get_export(&mut self.inner.store, function_name) |
467 | | { |
468 | 33 | Some(export) => match export.into_func() { |
469 | 32 | Some(f) => f, |
470 | 1 | None => return Err((StartErr::NotAFunction, self.inner)), |
471 | | }, |
472 | 2 | None => return Err((StartErr::FunctionNotFound, self.inner)), |
473 | | }; |
474 | | |
475 | | // Try to convert the signature of the function to call, in order to make sure |
476 | | // that the type of parameters and return value are supported. |
477 | 32 | let Ok(signature31 ) = Signature::try_from(&function_to_call.ty(&self.inner.store)) else { |
478 | 1 | return Err((StartErr::SignatureNotSupported, self.inner)); |
479 | | }; |
480 | | |
481 | | // Check the types of the provided parameters. |
482 | 31 | if params.len() != signature.parameters().len() { |
483 | 2 | return Err((StartErr::InvalidParameters, self.inner)); |
484 | 29 | } |
485 | 40 | for (obtained, expected) in params29 .iter29 ().zip29 (signature29 .parameters29 ()) { |
486 | 40 | if obtained.ty() != *expected { |
487 | 0 | return Err((StartErr::InvalidParameters, self.inner)); |
488 | 40 | } |
489 | | } |
490 | | |
491 | | // This function only performs all the verifications and preparations, but the call isn't |
492 | | // actually started here because we might still need to potentially access `store` |
493 | | // before being in the context of a function handler. |
494 | | |
495 | | Ok(Jit { |
496 | 29 | base_components: self.inner.base_components, |
497 | | inner: JitInner::NotStarted { |
498 | 29 | store: self.inner.store, |
499 | 29 | function_to_call, |
500 | 40 | params: params29 .iter29 ().map29 (|v| (*v).into()).collect29 ::<Vec<_>>(), _RNCNvMs1_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB7_7Prepare5start0Bd_ Line | Count | Source | 500 | 38 | params: params.iter().map(|v| (*v).into()).collect::<Vec<_>>(), |
_RNCNvMs1_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB7_7Prepare5start0Bd_ Line | Count | Source | 500 | 2 | params: params.iter().map(|v| (*v).into()).collect::<Vec<_>>(), |
|
501 | | }, |
502 | 29 | shared: self.inner.shared, |
503 | 29 | memory: self.inner.memory, |
504 | 29 | memory_type: self.inner.memory_type, |
505 | | }) |
506 | 35 | } _RNvMs1_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB5_7Prepare5start Line | Count | Source | 458 | 34 | pub fn start( | 459 | 34 | mut self, | 460 | 34 | function_name: &str, | 461 | 34 | params: &[WasmValue], | 462 | 34 | ) -> Result<Jit, (StartErr, JitPrototype)> { | 463 | 34 | let function_to_call31 = match self | 464 | 34 | .inner | 465 | 34 | .instance | 466 | 34 | .get_export(&mut self.inner.store, function_name) | 467 | | { | 468 | 32 | Some(export) => match export.into_func() { | 469 | 31 | Some(f) => f, | 470 | 1 | None => return Err((StartErr::NotAFunction, self.inner)), | 471 | | }, | 472 | 2 | None => return Err((StartErr::FunctionNotFound, self.inner)), | 473 | | }; | 474 | | | 475 | | // Try to convert the signature of the function to call, in order to make sure | 476 | | // that the type of parameters and return value are supported. | 477 | 31 | let Ok(signature30 ) = Signature::try_from(&function_to_call.ty(&self.inner.store)) else { | 478 | 1 | return Err((StartErr::SignatureNotSupported, self.inner)); | 479 | | }; | 480 | | | 481 | | // Check the types of the provided parameters. | 482 | 30 | if params.len() != signature.parameters().len() { | 483 | 2 | return Err((StartErr::InvalidParameters, self.inner)); | 484 | 28 | } | 485 | 38 | for (obtained, expected) in params28 .iter28 ().zip28 (signature28 .parameters28 ()) { | 486 | 38 | if obtained.ty() != *expected { | 487 | 0 | return Err((StartErr::InvalidParameters, self.inner)); | 488 | 38 | } | 489 | | } | 490 | | | 491 | | // This function only performs all the verifications and preparations, but the call isn't | 492 | | // actually started here because we might still need to potentially access `store` | 493 | | // before being in the context of a function handler. | 494 | | | 495 | | Ok(Jit { | 496 | 28 | base_components: self.inner.base_components, | 497 | | inner: JitInner::NotStarted { | 498 | 28 | store: self.inner.store, | 499 | 28 | function_to_call, | 500 | 28 | params: params.iter().map(|v| (*v).into()).collect::<Vec<_>>(), | 501 | | }, | 502 | 28 | shared: self.inner.shared, | 503 | 28 | memory: self.inner.memory, | 504 | 28 | memory_type: self.inner.memory_type, | 505 | | }) | 506 | 34 | } |
_RNvMs1_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB5_7Prepare5start Line | Count | Source | 458 | 1 | pub fn start( | 459 | 1 | mut self, | 460 | 1 | function_name: &str, | 461 | 1 | params: &[WasmValue], | 462 | 1 | ) -> Result<Jit, (StartErr, JitPrototype)> { | 463 | 1 | let function_to_call = match self | 464 | 1 | .inner | 465 | 1 | .instance | 466 | 1 | .get_export(&mut self.inner.store, function_name) | 467 | | { | 468 | 1 | Some(export) => match export.into_func() { | 469 | 1 | Some(f) => f, | 470 | 0 | None => return Err((StartErr::NotAFunction, self.inner)), | 471 | | }, | 472 | 0 | None => return Err((StartErr::FunctionNotFound, self.inner)), | 473 | | }; | 474 | | | 475 | | // Try to convert the signature of the function to call, in order to make sure | 476 | | // that the type of parameters and return value are supported. | 477 | 1 | let Ok(signature) = Signature::try_from(&function_to_call.ty(&self.inner.store)) else { | 478 | 0 | return Err((StartErr::SignatureNotSupported, self.inner)); | 479 | | }; | 480 | | | 481 | | // Check the types of the provided parameters. | 482 | 1 | if params.len() != signature.parameters().len() { | 483 | 0 | return Err((StartErr::InvalidParameters, self.inner)); | 484 | 1 | } | 485 | 2 | for (obtained, expected) in params1 .iter1 ().zip1 (signature1 .parameters1 ()) { | 486 | 2 | if obtained.ty() != *expected { | 487 | 0 | return Err((StartErr::InvalidParameters, self.inner)); | 488 | 2 | } | 489 | | } | 490 | | | 491 | | // This function only performs all the verifications and preparations, but the call isn't | 492 | | // actually started here because we might still need to potentially access `store` | 493 | | // before being in the context of a function handler. | 494 | | | 495 | | Ok(Jit { | 496 | 1 | base_components: self.inner.base_components, | 497 | | inner: JitInner::NotStarted { | 498 | 1 | store: self.inner.store, | 499 | 1 | function_to_call, | 500 | 1 | params: params.iter().map(|v| (*v).into()).collect::<Vec<_>>(), | 501 | | }, | 502 | 1 | shared: self.inner.shared, | 503 | 1 | memory: self.inner.memory, | 504 | 1 | memory_type: self.inner.memory_type, | 505 | | }) | 506 | 1 | } |
|
507 | | } |
508 | | |
509 | | impl fmt::Debug for Prepare { |
510 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
511 | 0 | f.debug_tuple("Prepare").finish() |
512 | 0 | } Unexecuted instantiation: _RNvXs2_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB5_7PrepareNtNtCs1p5UDGgVI4d_4core3fmt5Debug3fmt Unexecuted instantiation: _RNvXs2_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB5_7PrepareNtNtCs1p5UDGgVI4d_4core3fmt5Debug3fmt |
513 | | } |
514 | | |
515 | | /// Data shared between the external API and the functions that `wasmtime` directly invokes. |
516 | | /// |
517 | | /// The flow is as follows: |
518 | | /// |
519 | | /// - `wasmtime` calls a function that shares access to a `Arc<Mutex<Shared>>`. The `Shared` is in |
520 | | /// the [`Shared::OutsideFunctionCall`] state. |
521 | | /// - This function switches the state to the [`Shared::EnteredFunctionCall`] state and returns |
522 | | /// `Poll::Pending`. |
523 | | /// - This `Pending` gets propagated to the body of [`Jit::run`], which was calling `wasmtime`. |
524 | | /// [`Jit::run`] reads `function_index` and `parameters` to determine what happened, switches the |
525 | | /// state of the `Shared` to [`Shared::WithinFunctionCall`] state, and returns `Poll::Pending`. |
526 | | /// - Here, the user can access the memory, in which case the `Shared` is read. If the user wants |
527 | | /// to grow the memory, the state is switched to [`Shared::MemoryGrowRequired`], then execution |
528 | | /// resumed for the function to perform the growth and transition back to |
529 | | /// [`Shared::WithinFunctionCall`]. |
530 | | /// - Later, the state is switched to [`Shared::Return`], and execution is resumed. |
531 | | /// - The function called by `wasmtime` reads the return value and returns `Poll::Ready`. |
532 | | /// |
533 | | enum Shared { |
534 | | Poisoned, |
535 | | ExecutingStart, |
536 | | OutsideFunctionCall { |
537 | | memory: wasmtime::Memory, |
538 | | }, |
539 | | /// Function handler switches to this state as soon as it is entered, so that the host can |
540 | | /// pick up this state, extract the function index and parameters, and transition to |
541 | | /// [`Shared::WithinFunctionCall`]. |
542 | | EnteredFunctionCall { |
543 | | /// Index of the function currently being called. |
544 | | function_index: usize, |
545 | | /// Parameters of the function currently being called. |
546 | | parameters: Vec<WasmValue>, |
547 | | |
548 | | /// See [`Shared::WithinFunctionCall::memory`]. |
549 | | memory: SliceRawParts, |
550 | | /// See [`Shared::WithinFunctionCall::expected_return_ty`]. |
551 | | expected_return_ty: Option<ValueType>, |
552 | | /// See [`Shared::WithinFunctionCall::in_interrupted_waker`]. |
553 | | in_interrupted_waker: Option<task::Waker>, |
554 | | }, |
555 | | WithinFunctionCall { |
556 | | /// Pointer and size of the location where the virtual machine memory is located in the |
557 | | /// host memory. This pointer is invalidated if the memory is grown, which can happen |
558 | | /// between function calls. |
559 | | memory: SliceRawParts, |
560 | | |
561 | | /// Type of the return value of the function. |
562 | | expected_return_ty: Option<ValueType>, |
563 | | |
564 | | /// `Waker` that `wasmtime` has passed to the future that is waiting for `return_value`. |
565 | | /// This value is most likely not very useful, because [`Jit::run`] always polls the outer |
566 | | /// future whenever the inner future is known to be ready. |
567 | | /// However, it would be completely legal for `wasmtime` to not poll the inner future if the |
568 | | /// `waker` that it has passed (the one stored here) wasn't waken up. |
569 | | /// This field therefore exists in order to future-proof against this possible optimization |
570 | | /// that `wasmtime` might perform in the future. |
571 | | in_interrupted_waker: Option<task::Waker>, |
572 | | }, |
573 | | MemoryGrowRequired { |
574 | | memory: wasmtime::Memory, |
575 | | additional: u64, |
576 | | }, |
577 | | Return { |
578 | | /// Value to return to the Wasm code. |
579 | | return_value: Option<WasmValue>, |
580 | | memory: wasmtime::Memory, |
581 | | }, |
582 | | } |
583 | | |
584 | | /// This idiotic struct and unsafe code are necessary because Rust doesn't implement `Send` and |
585 | | /// `Sync` for raw pointers. |
586 | | #[derive(Copy, Clone)] |
587 | | struct SliceRawParts(*mut u8, usize); |
588 | | unsafe impl Send for SliceRawParts {} |
589 | | unsafe impl Sync for SliceRawParts {} |
590 | | |
591 | | /// See [`super::VirtualMachine`]. |
592 | | pub struct Jit { |
593 | | /// Base components that can be used to recreate a prototype later if desired. |
594 | | base_components: BaseComponents, |
595 | | |
596 | | inner: JitInner, |
597 | | |
598 | | /// Shared between the "outside" and the external functions. See [`Shared`]. |
599 | | shared: Arc<Mutex<Shared>>, |
600 | | |
601 | | /// See [`JitPrototype::memory`]. |
602 | | memory: wasmtime::Memory, |
603 | | |
604 | | /// See [`JitPrototype::memory_type`]. |
605 | | memory_type: wasmtime::MemoryType, |
606 | | } |
607 | | |
608 | | enum JitInner { |
609 | | Poisoned, |
610 | | |
611 | | /// Execution has not started yet. |
612 | | NotStarted { |
613 | | store: wasmtime::Store<()>, |
614 | | function_to_call: wasmtime::Func, |
615 | | params: Vec<wasmtime::Val>, |
616 | | }, |
617 | | /// `Future` that drives the execution. Contains an invocation of `wasmtime::Func::call_async`. |
618 | | Executing(BoxFuture<(wasmtime::Store<()>, ExecOutcomeValue)>), |
619 | | /// Execution has finished because the future has returned `Poll::Ready` in the past. |
620 | | Done(wasmtime::Store<()>), |
621 | | } |
622 | | |
623 | | type BoxFuture<T> = pin::Pin<Box<dyn Future<Output = T> + Send>>; |
624 | | type ExecOutcomeValue = Result<Option<WasmValue>, wasmtime::Error>; |
625 | | |
626 | | impl Jit { |
627 | | /// See [`super::VirtualMachine::run`]. |
628 | 4.21k | pub fn run(&mut self, value: Option<WasmValue>) -> Result<ExecOutcome, RunErr> { |
629 | | // Make sure that `self.inner` is in `JitInner::Executing` start, starting the call if |
630 | | // necessary. |
631 | 4.21k | match self.inner { |
632 | | JitInner::Executing(_) => { |
633 | | // Virtual machine was already executing. Update `Shared` to store the return |
634 | | // value, so that the function handler picks it up and returns it to `wasmtime`. |
635 | 4.19k | let mut shared_lock = self.shared.try_lock().unwrap(); |
636 | 4.19k | match mem::replace(&mut *shared_lock, Shared::Poisoned) { |
637 | | Shared::WithinFunctionCall { |
638 | 4.19k | in_interrupted_waker, |
639 | 4.19k | expected_return_ty, |
640 | 4.19k | memory, |
641 | | } => { |
642 | 4.19k | let provided_value_ty = value.as_ref().map(|v| v2.10k .ty2.10k ()); _RNCNvMs5_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB7_3Jit3run0Bd_ Line | Count | Source | 642 | 28 | let provided_value_ty = value.as_ref().map(|v| v.ty()); |
_RNCNvMs5_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB7_3Jit3run0Bd_ Line | Count | Source | 642 | 2.07k | let provided_value_ty = value.as_ref().map(|v| v.ty()); |
|
643 | 4.19k | if expected_return_ty != provided_value_ty { |
644 | 1 | *shared_lock = Shared::WithinFunctionCall { |
645 | 1 | in_interrupted_waker, |
646 | 1 | expected_return_ty, |
647 | 1 | memory, |
648 | 1 | }; |
649 | 1 | return Err(RunErr::BadValueTy { |
650 | 1 | expected: expected_return_ty, |
651 | 1 | obtained: provided_value_ty, |
652 | 1 | }); |
653 | 4.19k | } |
654 | | |
655 | 4.19k | *shared_lock = Shared::Return { |
656 | 4.19k | return_value: value, |
657 | 4.19k | memory: self.memory, |
658 | 4.19k | }; |
659 | | |
660 | 4.19k | if let Some(waker) = in_interrupted_waker { |
661 | 4.19k | waker.wake(); |
662 | 4.19k | }0 |
663 | | } |
664 | 0 | _ => unreachable!(), |
665 | | } |
666 | | } |
667 | 0 | JitInner::Done(_) => return Err(RunErr::Poisoned), |
668 | 0 | JitInner::Poisoned => unreachable!(), |
669 | | JitInner::NotStarted { .. } => { |
670 | 25 | if value.is_some() { |
671 | | return Err(RunErr::BadValueTy { |
672 | 1 | expected: None, |
673 | 1 | obtained: value.as_ref().map(|v| v.ty()), _RNCNvMs5_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB7_3Jit3runs_0Bd_ Line | Count | Source | 673 | 1 | obtained: value.as_ref().map(|v| v.ty()), |
Unexecuted instantiation: _RNCNvMs5_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB7_3Jit3runs_0Bd_ |
674 | | }); |
675 | 24 | } |
676 | | |
677 | 24 | let (function_to_call, params, mut store) = |
678 | 24 | match mem::replace(&mut self.inner, JitInner::Poisoned) { |
679 | | JitInner::NotStarted { |
680 | 24 | function_to_call, |
681 | 24 | params, |
682 | 24 | store, |
683 | 24 | } => (function_to_call, params, store), |
684 | 0 | _ => unreachable!(), |
685 | | }; |
686 | | |
687 | 24 | *self.shared.try_lock().unwrap() = Shared::OutsideFunctionCall { |
688 | 24 | memory: self.memory, |
689 | 24 | }; |
690 | | |
691 | | // Check whether the function to call has a return value. |
692 | | // We made sure when starting that the signature was supported. |
693 | 24 | let has_return_value = Signature::try_from(&function_to_call.ty(&store)) |
694 | 24 | .unwrap() |
695 | 24 | .return_type() |
696 | 24 | .is_some(); |
697 | | |
698 | | // Starting the function call. |
699 | 24 | let function_call = Box::pin(async move { |
700 | | // Prepare an array of results to pass to `wasmtime`. Note that the type doesn't |
701 | | // have to match the actual return value, only the length. |
702 | 24 | let mut result = [wasmtime::Val::I32(0)]; |
703 | | |
704 | 24 | let outcome21 = function_to_call |
705 | 24 | .call_async( |
706 | 24 | &mut store, |
707 | 24 | ¶ms, |
708 | 24 | &mut result[..(if has_return_value { 123 } else { 01 })], |
709 | | ) |
710 | 24 | .await; |
711 | | |
712 | | // Execution resumes here when the Wasm code has finished, gracefully or not. |
713 | 20 | match outcome { |
714 | 19 | Ok(()) if has_return_value => { |
715 | | // TODO: could implement TryFrom on wasmtime::Val instead of &wasmtime::Val to avoid borrow here? |
716 | 19 | (store, Ok(Some((&result[0]).try_into().unwrap()))) |
717 | | } |
718 | 1 | Ok(()) => (store, Ok(None)), |
719 | 1 | Err(err) => (store, Err(err)), |
720 | | } |
721 | 21 | }); _RNCNvMs5_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB7_3Jit3runs0_0Bd_ Line | Count | Source | 699 | 23 | let function_call = Box::pin(async move { | 700 | | // Prepare an array of results to pass to `wasmtime`. Note that the type doesn't | 701 | | // have to match the actual return value, only the length. | 702 | 23 | let mut result = [wasmtime::Val::I32(0)]; | 703 | | | 704 | 23 | let outcome20 = function_to_call | 705 | 23 | .call_async( | 706 | 23 | &mut store, | 707 | 23 | ¶ms, | 708 | 23 | &mut result[..(if has_return_value { 122 } else { 01 })], | 709 | | ) | 710 | 23 | .await; | 711 | | | 712 | | // Execution resumes here when the Wasm code has finished, gracefully or not. | 713 | 19 | match outcome { | 714 | 18 | Ok(()) if has_return_value => { | 715 | | // TODO: could implement TryFrom on wasmtime::Val instead of &wasmtime::Val to avoid borrow here? | 716 | 18 | (store, Ok(Some((&result[0]).try_into().unwrap()))) | 717 | | } | 718 | 1 | Ok(()) => (store, Ok(None)), | 719 | 1 | Err(err) => (store, Err(err)), | 720 | | } | 721 | 20 | }); |
_RNCNvMs5_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB7_3Jit3runs0_0Bd_ Line | Count | Source | 699 | 1 | let function_call = Box::pin(async move { | 700 | | // Prepare an array of results to pass to `wasmtime`. Note that the type doesn't | 701 | | // have to match the actual return value, only the length. | 702 | 1 | let mut result = [wasmtime::Val::I32(0)]; | 703 | | | 704 | 1 | let outcome = function_to_call | 705 | 1 | .call_async( | 706 | 1 | &mut store, | 707 | 1 | ¶ms, | 708 | 1 | &mut result[..(if has_return_value { 1 } else { 00 })], | 709 | | ) | 710 | 1 | .await; | 711 | | | 712 | | // Execution resumes here when the Wasm code has finished, gracefully or not. | 713 | 1 | match outcome { | 714 | 1 | Ok(()) if has_return_value => { | 715 | | // TODO: could implement TryFrom on wasmtime::Val instead of &wasmtime::Val to avoid borrow here? | 716 | 1 | (store, Ok(Some((&result[0]).try_into().unwrap()))) | 717 | | } | 718 | 0 | Ok(()) => (store, Ok(None)), | 719 | 0 | Err(err) => (store, Err(err)), | 720 | | } | 721 | 1 | }); |
|
722 | | |
723 | 24 | self.inner = JitInner::Executing(function_call); |
724 | | } |
725 | | }; |
726 | | |
727 | | // We made sure that the state is in `Executing`. Now grab the future. |
728 | 4.21k | let function_call = match &mut self.inner { |
729 | 4.21k | JitInner::Executing(f) => f, |
730 | 0 | _ => unreachable!(), |
731 | | }; |
732 | | |
733 | | // Resume the coroutine execution. |
734 | | // The `Future` is polled with a no-op waker. We are in total control of when the |
735 | | // execution might be able to progress, hence the lack of need for a waker. |
736 | 4.21k | match Future::poll( |
737 | 4.21k | function_call.as_mut(), |
738 | 4.21k | &mut task::Context::from_waker(task::Waker::noop()), |
739 | | ) { |
740 | 20 | task::Poll::Ready((store, Ok(val))) => { |
741 | 20 | self.inner = JitInner::Done(store); |
742 | 20 | Ok(ExecOutcome::Finished { |
743 | 20 | // Since we verify at initialization that the signature of the function to |
744 | 20 | // call is supported, it is guaranteed that the type of this return value is |
745 | 20 | // supported too. |
746 | 20 | return_value: Ok(val), |
747 | 20 | }) |
748 | | } |
749 | 1 | task::Poll::Ready((store, Err(err))) => { |
750 | 1 | self.inner = JitInner::Done(store); |
751 | 1 | Ok(ExecOutcome::Finished { |
752 | 1 | return_value: Err(Trap(err.to_string())), |
753 | 1 | }) |
754 | | } |
755 | | task::Poll::Pending => { |
756 | 4.19k | let mut shared_lock = self.shared.try_lock().unwrap(); |
757 | 4.19k | match mem::replace(&mut *shared_lock, Shared::Poisoned) { |
758 | | Shared::EnteredFunctionCall { |
759 | 4.19k | function_index, |
760 | 4.19k | parameters, |
761 | 4.19k | memory, |
762 | 4.19k | expected_return_ty, |
763 | 4.19k | in_interrupted_waker, |
764 | | } => { |
765 | 4.19k | *shared_lock = Shared::WithinFunctionCall { |
766 | 4.19k | memory, |
767 | 4.19k | expected_return_ty, |
768 | 4.19k | in_interrupted_waker, |
769 | 4.19k | }; |
770 | | |
771 | 4.19k | Ok(ExecOutcome::Interrupted { |
772 | 4.19k | id: function_index, |
773 | 4.19k | params: parameters, |
774 | 4.19k | }) |
775 | | } |
776 | 0 | _ => unreachable!(), |
777 | | } |
778 | | } |
779 | | } |
780 | 4.21k | } _RNvMs5_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB5_3Jit3run Line | Count | Source | 628 | 66 | pub fn run(&mut self, value: Option<WasmValue>) -> Result<ExecOutcome, RunErr> { | 629 | | // Make sure that `self.inner` is in `JitInner::Executing` start, starting the call if | 630 | | // necessary. | 631 | 66 | match self.inner { | 632 | | JitInner::Executing(_) => { | 633 | | // Virtual machine was already executing. Update `Shared` to store the return | 634 | | // value, so that the function handler picks it up and returns it to `wasmtime`. | 635 | 42 | let mut shared_lock = self.shared.try_lock().unwrap(); | 636 | 42 | match mem::replace(&mut *shared_lock, Shared::Poisoned) { | 637 | | Shared::WithinFunctionCall { | 638 | 42 | in_interrupted_waker, | 639 | 42 | expected_return_ty, | 640 | 42 | memory, | 641 | | } => { | 642 | 42 | let provided_value_ty = value.as_ref().map(|v| v.ty()); | 643 | 42 | if expected_return_ty != provided_value_ty { | 644 | 1 | *shared_lock = Shared::WithinFunctionCall { | 645 | 1 | in_interrupted_waker, | 646 | 1 | expected_return_ty, | 647 | 1 | memory, | 648 | 1 | }; | 649 | 1 | return Err(RunErr::BadValueTy { | 650 | 1 | expected: expected_return_ty, | 651 | 1 | obtained: provided_value_ty, | 652 | 1 | }); | 653 | 41 | } | 654 | | | 655 | 41 | *shared_lock = Shared::Return { | 656 | 41 | return_value: value, | 657 | 41 | memory: self.memory, | 658 | 41 | }; | 659 | | | 660 | 41 | if let Some(waker) = in_interrupted_waker { | 661 | 41 | waker.wake(); | 662 | 41 | }0 | 663 | | } | 664 | 0 | _ => unreachable!(), | 665 | | } | 666 | | } | 667 | 0 | JitInner::Done(_) => return Err(RunErr::Poisoned), | 668 | 0 | JitInner::Poisoned => unreachable!(), | 669 | | JitInner::NotStarted { .. } => { | 670 | 24 | if value.is_some() { | 671 | | return Err(RunErr::BadValueTy { | 672 | 1 | expected: None, | 673 | 1 | obtained: value.as_ref().map(|v| v.ty()), | 674 | | }); | 675 | 23 | } | 676 | | | 677 | 23 | let (function_to_call, params, mut store) = | 678 | 23 | match mem::replace(&mut self.inner, JitInner::Poisoned) { | 679 | | JitInner::NotStarted { | 680 | 23 | function_to_call, | 681 | 23 | params, | 682 | 23 | store, | 683 | 23 | } => (function_to_call, params, store), | 684 | 0 | _ => unreachable!(), | 685 | | }; | 686 | | | 687 | 23 | *self.shared.try_lock().unwrap() = Shared::OutsideFunctionCall { | 688 | 23 | memory: self.memory, | 689 | 23 | }; | 690 | | | 691 | | // Check whether the function to call has a return value. | 692 | | // We made sure when starting that the signature was supported. | 693 | 23 | let has_return_value = Signature::try_from(&function_to_call.ty(&store)) | 694 | 23 | .unwrap() | 695 | 23 | .return_type() | 696 | 23 | .is_some(); | 697 | | | 698 | | // Starting the function call. | 699 | 23 | let function_call = Box::pin(async move { | 700 | | // Prepare an array of results to pass to `wasmtime`. Note that the type doesn't | 701 | | // have to match the actual return value, only the length. | 702 | | let mut result = [wasmtime::Val::I32(0)]; | 703 | | | 704 | | let outcome = function_to_call | 705 | | .call_async( | 706 | | &mut store, | 707 | | ¶ms, | 708 | | &mut result[..(if has_return_value { 1 } else { 0 })], | 709 | | ) | 710 | | .await; | 711 | | | 712 | | // Execution resumes here when the Wasm code has finished, gracefully or not. | 713 | | match outcome { | 714 | | Ok(()) if has_return_value => { | 715 | | // TODO: could implement TryFrom on wasmtime::Val instead of &wasmtime::Val to avoid borrow here? | 716 | | (store, Ok(Some((&result[0]).try_into().unwrap()))) | 717 | | } | 718 | | Ok(()) => (store, Ok(None)), | 719 | | Err(err) => (store, Err(err)), | 720 | | } | 721 | | }); | 722 | | | 723 | 23 | self.inner = JitInner::Executing(function_call); | 724 | | } | 725 | | }; | 726 | | | 727 | | // We made sure that the state is in `Executing`. Now grab the future. | 728 | 64 | let function_call = match &mut self.inner { | 729 | 64 | JitInner::Executing(f) => f, | 730 | 0 | _ => unreachable!(), | 731 | | }; | 732 | | | 733 | | // Resume the coroutine execution. | 734 | | // The `Future` is polled with a no-op waker. We are in total control of when the | 735 | | // execution might be able to progress, hence the lack of need for a waker. | 736 | 64 | match Future::poll( | 737 | 64 | function_call.as_mut(), | 738 | 64 | &mut task::Context::from_waker(task::Waker::noop()), | 739 | | ) { | 740 | 19 | task::Poll::Ready((store, Ok(val))) => { | 741 | 19 | self.inner = JitInner::Done(store); | 742 | 19 | Ok(ExecOutcome::Finished { | 743 | 19 | // Since we verify at initialization that the signature of the function to | 744 | 19 | // call is supported, it is guaranteed that the type of this return value is | 745 | 19 | // supported too. | 746 | 19 | return_value: Ok(val), | 747 | 19 | }) | 748 | | } | 749 | 1 | task::Poll::Ready((store, Err(err))) => { | 750 | 1 | self.inner = JitInner::Done(store); | 751 | 1 | Ok(ExecOutcome::Finished { | 752 | 1 | return_value: Err(Trap(err.to_string())), | 753 | 1 | }) | 754 | | } | 755 | | task::Poll::Pending => { | 756 | 44 | let mut shared_lock = self.shared.try_lock().unwrap(); | 757 | 44 | match mem::replace(&mut *shared_lock, Shared::Poisoned) { | 758 | | Shared::EnteredFunctionCall { | 759 | 44 | function_index, | 760 | 44 | parameters, | 761 | 44 | memory, | 762 | 44 | expected_return_ty, | 763 | 44 | in_interrupted_waker, | 764 | | } => { | 765 | 44 | *shared_lock = Shared::WithinFunctionCall { | 766 | 44 | memory, | 767 | 44 | expected_return_ty, | 768 | 44 | in_interrupted_waker, | 769 | 44 | }; | 770 | | | 771 | 44 | Ok(ExecOutcome::Interrupted { | 772 | 44 | id: function_index, | 773 | 44 | params: parameters, | 774 | 44 | }) | 775 | | } | 776 | 0 | _ => unreachable!(), | 777 | | } | 778 | | } | 779 | | } | 780 | 66 | } |
_RNvMs5_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB5_3Jit3run Line | Count | Source | 628 | 4.15k | pub fn run(&mut self, value: Option<WasmValue>) -> Result<ExecOutcome, RunErr> { | 629 | | // Make sure that `self.inner` is in `JitInner::Executing` start, starting the call if | 630 | | // necessary. | 631 | 4.15k | match self.inner { | 632 | | JitInner::Executing(_) => { | 633 | | // Virtual machine was already executing. Update `Shared` to store the return | 634 | | // value, so that the function handler picks it up and returns it to `wasmtime`. | 635 | 4.15k | let mut shared_lock = self.shared.try_lock().unwrap(); | 636 | 4.15k | match mem::replace(&mut *shared_lock, Shared::Poisoned) { | 637 | | Shared::WithinFunctionCall { | 638 | 4.15k | in_interrupted_waker, | 639 | 4.15k | expected_return_ty, | 640 | 4.15k | memory, | 641 | | } => { | 642 | 4.15k | let provided_value_ty = value.as_ref().map(|v| v.ty()); | 643 | 4.15k | if expected_return_ty != provided_value_ty { | 644 | 0 | *shared_lock = Shared::WithinFunctionCall { | 645 | 0 | in_interrupted_waker, | 646 | 0 | expected_return_ty, | 647 | 0 | memory, | 648 | 0 | }; | 649 | 0 | return Err(RunErr::BadValueTy { | 650 | 0 | expected: expected_return_ty, | 651 | 0 | obtained: provided_value_ty, | 652 | 0 | }); | 653 | 4.15k | } | 654 | | | 655 | 4.15k | *shared_lock = Shared::Return { | 656 | 4.15k | return_value: value, | 657 | 4.15k | memory: self.memory, | 658 | 4.15k | }; | 659 | | | 660 | 4.15k | if let Some(waker) = in_interrupted_waker { | 661 | 4.15k | waker.wake(); | 662 | 4.15k | }0 | 663 | | } | 664 | 0 | _ => unreachable!(), | 665 | | } | 666 | | } | 667 | 0 | JitInner::Done(_) => return Err(RunErr::Poisoned), | 668 | 0 | JitInner::Poisoned => unreachable!(), | 669 | | JitInner::NotStarted { .. } => { | 670 | 1 | if value.is_some() { | 671 | | return Err(RunErr::BadValueTy { | 672 | 0 | expected: None, | 673 | 0 | obtained: value.as_ref().map(|v| v.ty()), | 674 | | }); | 675 | 1 | } | 676 | | | 677 | 1 | let (function_to_call, params, mut store) = | 678 | 1 | match mem::replace(&mut self.inner, JitInner::Poisoned) { | 679 | | JitInner::NotStarted { | 680 | 1 | function_to_call, | 681 | 1 | params, | 682 | 1 | store, | 683 | 1 | } => (function_to_call, params, store), | 684 | 0 | _ => unreachable!(), | 685 | | }; | 686 | | | 687 | 1 | *self.shared.try_lock().unwrap() = Shared::OutsideFunctionCall { | 688 | 1 | memory: self.memory, | 689 | 1 | }; | 690 | | | 691 | | // Check whether the function to call has a return value. | 692 | | // We made sure when starting that the signature was supported. | 693 | 1 | let has_return_value = Signature::try_from(&function_to_call.ty(&store)) | 694 | 1 | .unwrap() | 695 | 1 | .return_type() | 696 | 1 | .is_some(); | 697 | | | 698 | | // Starting the function call. | 699 | 1 | let function_call = Box::pin(async move { | 700 | | // Prepare an array of results to pass to `wasmtime`. Note that the type doesn't | 701 | | // have to match the actual return value, only the length. | 702 | | let mut result = [wasmtime::Val::I32(0)]; | 703 | | | 704 | | let outcome = function_to_call | 705 | | .call_async( | 706 | | &mut store, | 707 | | ¶ms, | 708 | | &mut result[..(if has_return_value { 1 } else { 0 })], | 709 | | ) | 710 | | .await; | 711 | | | 712 | | // Execution resumes here when the Wasm code has finished, gracefully or not. | 713 | | match outcome { | 714 | | Ok(()) if has_return_value => { | 715 | | // TODO: could implement TryFrom on wasmtime::Val instead of &wasmtime::Val to avoid borrow here? | 716 | | (store, Ok(Some((&result[0]).try_into().unwrap()))) | 717 | | } | 718 | | Ok(()) => (store, Ok(None)), | 719 | | Err(err) => (store, Err(err)), | 720 | | } | 721 | | }); | 722 | | | 723 | 1 | self.inner = JitInner::Executing(function_call); | 724 | | } | 725 | | }; | 726 | | | 727 | | // We made sure that the state is in `Executing`. Now grab the future. | 728 | 4.15k | let function_call = match &mut self.inner { | 729 | 4.15k | JitInner::Executing(f) => f, | 730 | 0 | _ => unreachable!(), | 731 | | }; | 732 | | | 733 | | // Resume the coroutine execution. | 734 | | // The `Future` is polled with a no-op waker. We are in total control of when the | 735 | | // execution might be able to progress, hence the lack of need for a waker. | 736 | 4.15k | match Future::poll( | 737 | 4.15k | function_call.as_mut(), | 738 | 4.15k | &mut task::Context::from_waker(task::Waker::noop()), | 739 | | ) { | 740 | 1 | task::Poll::Ready((store, Ok(val))) => { | 741 | 1 | self.inner = JitInner::Done(store); | 742 | 1 | Ok(ExecOutcome::Finished { | 743 | 1 | // Since we verify at initialization that the signature of the function to | 744 | 1 | // call is supported, it is guaranteed that the type of this return value is | 745 | 1 | // supported too. | 746 | 1 | return_value: Ok(val), | 747 | 1 | }) | 748 | | } | 749 | 0 | task::Poll::Ready((store, Err(err))) => { | 750 | 0 | self.inner = JitInner::Done(store); | 751 | 0 | Ok(ExecOutcome::Finished { | 752 | 0 | return_value: Err(Trap(err.to_string())), | 753 | 0 | }) | 754 | | } | 755 | | task::Poll::Pending => { | 756 | 4.15k | let mut shared_lock = self.shared.try_lock().unwrap(); | 757 | 4.15k | match mem::replace(&mut *shared_lock, Shared::Poisoned) { | 758 | | Shared::EnteredFunctionCall { | 759 | 4.15k | function_index, | 760 | 4.15k | parameters, | 761 | 4.15k | memory, | 762 | 4.15k | expected_return_ty, | 763 | 4.15k | in_interrupted_waker, | 764 | | } => { | 765 | 4.15k | *shared_lock = Shared::WithinFunctionCall { | 766 | 4.15k | memory, | 767 | 4.15k | expected_return_ty, | 768 | 4.15k | in_interrupted_waker, | 769 | 4.15k | }; | 770 | | | 771 | 4.15k | Ok(ExecOutcome::Interrupted { | 772 | 4.15k | id: function_index, | 773 | 4.15k | params: parameters, | 774 | 4.15k | }) | 775 | | } | 776 | 0 | _ => unreachable!(), | 777 | | } | 778 | | } | 779 | | } | 780 | 4.15k | } |
|
781 | | |
782 | | /// See [`super::VirtualMachine::memory_size`]. |
783 | 8.76k | pub fn memory_size(&self) -> HeapPages { |
784 | 8.76k | match &self.inner { |
785 | 18 | JitInner::NotStarted { store4 , .. } | JitInner::Done(store) => { |
786 | 22 | let heap_pages = self.memory.size(store); |
787 | 22 | HeapPages::new(u32::try_from(heap_pages).unwrap()) |
788 | | } |
789 | | JitInner::Executing(_) => { |
790 | 8.74k | let size_bytes = match *self.shared.try_lock().unwrap() { |
791 | 8.74k | Shared::WithinFunctionCall { memory, .. } => memory.1, |
792 | 0 | _ => unreachable!(), |
793 | | }; |
794 | | |
795 | 8.74k | if size_bytes == 0 { |
796 | 0 | HeapPages::new(0) |
797 | | } else { |
798 | 8.74k | HeapPages::new(1 + u32::try_from((size_bytes - 1) / (64 * 1024)).unwrap()) |
799 | | } |
800 | | } |
801 | 0 | JitInner::Poisoned => unreachable!(), |
802 | | } |
803 | 8.76k | } _RNvMs5_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB5_3Jit11memory_size Line | Count | Source | 783 | 95 | pub fn memory_size(&self) -> HeapPages { | 784 | 95 | match &self.inner { | 785 | 17 | JitInner::NotStarted { store4 , .. } | JitInner::Done(store) => { | 786 | 21 | let heap_pages = self.memory.size(store); | 787 | 21 | HeapPages::new(u32::try_from(heap_pages).unwrap()) | 788 | | } | 789 | | JitInner::Executing(_) => { | 790 | 74 | let size_bytes = match *self.shared.try_lock().unwrap() { | 791 | 74 | Shared::WithinFunctionCall { memory, .. } => memory.1, | 792 | 0 | _ => unreachable!(), | 793 | | }; | 794 | | | 795 | 74 | if size_bytes == 0 { | 796 | 0 | HeapPages::new(0) | 797 | | } else { | 798 | 74 | HeapPages::new(1 + u32::try_from((size_bytes - 1) / (64 * 1024)).unwrap()) | 799 | | } | 800 | | } | 801 | 0 | JitInner::Poisoned => unreachable!(), | 802 | | } | 803 | 95 | } |
_RNvMs5_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB5_3Jit11memory_size Line | Count | Source | 783 | 8.67k | pub fn memory_size(&self) -> HeapPages { | 784 | 8.67k | match &self.inner { | 785 | 1 | JitInner::NotStarted { store0 , .. } | JitInner::Done(store) => { | 786 | 1 | let heap_pages = self.memory.size(store); | 787 | 1 | HeapPages::new(u32::try_from(heap_pages).unwrap()) | 788 | | } | 789 | | JitInner::Executing(_) => { | 790 | 8.67k | let size_bytes = match *self.shared.try_lock().unwrap() { | 791 | 8.67k | Shared::WithinFunctionCall { memory, .. } => memory.1, | 792 | 0 | _ => unreachable!(), | 793 | | }; | 794 | | | 795 | 8.67k | if size_bytes == 0 { | 796 | 0 | HeapPages::new(0) | 797 | | } else { | 798 | 8.67k | HeapPages::new(1 + u32::try_from((size_bytes - 1) / (64 * 1024)).unwrap()) | 799 | | } | 800 | | } | 801 | 0 | JitInner::Poisoned => unreachable!(), | 802 | | } | 803 | 8.67k | } |
|
804 | | |
805 | | /// See [`super::VirtualMachine::read_memory`]. |
806 | 2.48k | pub fn read_memory( |
807 | 2.48k | &self, |
808 | 2.48k | offset: u32, |
809 | 2.48k | size: u32, |
810 | 2.48k | ) -> Result<impl AsRef<[u8]>, OutOfBoundsError> { |
811 | 2.48k | let memory_slice = match &self.inner { |
812 | 18 | JitInner::NotStarted { store4 , .. } | JitInner::Done(store14 ) => self.memory.data(store), |
813 | | JitInner::Executing(_) => { |
814 | 2.46k | let memory = match *self.shared.try_lock().unwrap() { |
815 | 2.46k | Shared::WithinFunctionCall { memory, .. } => memory, |
816 | 0 | _ => unreachable!(), |
817 | | }; |
818 | | |
819 | 2.46k | unsafe { slice::from_raw_parts(memory.0, memory.1) } |
820 | | } |
821 | 0 | JitInner::Poisoned => unreachable!(), |
822 | | }; |
823 | | |
824 | 2.48k | let start = usize::try_from(offset).map_err(|_| OutOfBoundsError)?0 ; |
825 | 2.48k | let end = start |
826 | 2.48k | .checked_add(usize::try_from(size).map_err(|_| OutOfBoundsError)?0 ) |
827 | 2.48k | .ok_or(OutOfBoundsError)?0 ; |
828 | | |
829 | 2.48k | if end > memory_slice.len() { |
830 | 0 | return Err(OutOfBoundsError); |
831 | 2.48k | } |
832 | | |
833 | 2.48k | Ok(&memory_slice[start..end]) |
834 | 2.48k | } _RNvMs5_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB5_3Jit11read_memory Line | Count | Source | 806 | 38 | pub fn read_memory( | 807 | 38 | &self, | 808 | 38 | offset: u32, | 809 | 38 | size: u32, | 810 | 38 | ) -> Result<impl AsRef<[u8]>, OutOfBoundsError> { | 811 | 38 | let memory_slice = match &self.inner { | 812 | 17 | JitInner::NotStarted { store4 , .. } | JitInner::Done(store13 ) => self.memory.data(store), | 813 | | JitInner::Executing(_) => { | 814 | 21 | let memory = match *self.shared.try_lock().unwrap() { | 815 | 21 | Shared::WithinFunctionCall { memory, .. } => memory, | 816 | 0 | _ => unreachable!(), | 817 | | }; | 818 | | | 819 | 21 | unsafe { slice::from_raw_parts(memory.0, memory.1) } | 820 | | } | 821 | 0 | JitInner::Poisoned => unreachable!(), | 822 | | }; | 823 | | | 824 | 38 | let start = usize::try_from(offset).map_err(|_| OutOfBoundsError)?0 ; | 825 | 38 | let end = start | 826 | 38 | .checked_add(usize::try_from(size).map_err(|_| OutOfBoundsError)?0 ) | 827 | 38 | .ok_or(OutOfBoundsError)?0 ; | 828 | | | 829 | 38 | if end > memory_slice.len() { | 830 | 0 | return Err(OutOfBoundsError); | 831 | 38 | } | 832 | | | 833 | 38 | Ok(&memory_slice[start..end]) | 834 | 38 | } |
_RNvMs5_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB5_3Jit11read_memory Line | Count | Source | 806 | 2.44k | pub fn read_memory( | 807 | 2.44k | &self, | 808 | 2.44k | offset: u32, | 809 | 2.44k | size: u32, | 810 | 2.44k | ) -> Result<impl AsRef<[u8]>, OutOfBoundsError> { | 811 | 2.44k | let memory_slice = match &self.inner { | 812 | 1 | JitInner::NotStarted { store0 , .. } | JitInner::Done(store) => self.memory.data(store), | 813 | | JitInner::Executing(_) => { | 814 | 2.44k | let memory = match *self.shared.try_lock().unwrap() { | 815 | 2.44k | Shared::WithinFunctionCall { memory, .. } => memory, | 816 | 0 | _ => unreachable!(), | 817 | | }; | 818 | | | 819 | 2.44k | unsafe { slice::from_raw_parts(memory.0, memory.1) } | 820 | | } | 821 | 0 | JitInner::Poisoned => unreachable!(), | 822 | | }; | 823 | | | 824 | 2.44k | let start = usize::try_from(offset).map_err(|_| OutOfBoundsError)?0 ; | 825 | 2.44k | let end = start | 826 | 2.44k | .checked_add(usize::try_from(size).map_err(|_| OutOfBoundsError)?0 ) | 827 | 2.44k | .ok_or(OutOfBoundsError)?0 ; | 828 | | | 829 | 2.44k | if end > memory_slice.len() { | 830 | 0 | return Err(OutOfBoundsError); | 831 | 2.44k | } | 832 | | | 833 | 2.44k | Ok(&memory_slice[start..end]) | 834 | 2.44k | } |
|
835 | | |
836 | | /// See [`super::VirtualMachine::write_memory`]. |
837 | 4.20k | pub fn write_memory(&mut self, offset: u32, value: &[u8]) -> Result<(), OutOfBoundsError> { |
838 | 4.20k | let memory_slice = match &mut self.inner { |
839 | 1 | JitInner::NotStarted { store, .. } | JitInner::Done(store0 ) => { |
840 | 1 | self.memory.data_mut(store) |
841 | | } |
842 | | JitInner::Executing(_) => { |
843 | 4.19k | let memory = match *self.shared.try_lock().unwrap() { |
844 | 4.19k | Shared::WithinFunctionCall { memory, .. } => memory, |
845 | 0 | _ => unreachable!(), |
846 | | }; |
847 | | |
848 | 4.19k | unsafe { slice::from_raw_parts_mut(memory.0, memory.1) } |
849 | | } |
850 | 0 | JitInner::Poisoned => unreachable!(), |
851 | | }; |
852 | | |
853 | 4.20k | let start = usize::try_from(offset).map_err(|_| OutOfBoundsError)?0 ; |
854 | 4.20k | let end = start.checked_add(value.len()).ok_or(OutOfBoundsError)?0 ; |
855 | | |
856 | 4.20k | if end > memory_slice.len() { |
857 | 0 | return Err(OutOfBoundsError); |
858 | 4.20k | } |
859 | | |
860 | 4.20k | if !value.is_empty() { |
861 | 4.20k | memory_slice[start..end].copy_from_slice(value); |
862 | 4.20k | }0 |
863 | | |
864 | 4.20k | Ok(()) |
865 | 4.20k | } _RNvMs5_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB5_3Jit12write_memory Line | Count | Source | 837 | 49 | pub fn write_memory(&mut self, offset: u32, value: &[u8]) -> Result<(), OutOfBoundsError> { | 838 | 49 | let memory_slice = match &mut self.inner { | 839 | 1 | JitInner::NotStarted { store, .. } | JitInner::Done(store0 ) => { | 840 | 1 | self.memory.data_mut(store) | 841 | | } | 842 | | JitInner::Executing(_) => { | 843 | 48 | let memory = match *self.shared.try_lock().unwrap() { | 844 | 48 | Shared::WithinFunctionCall { memory, .. } => memory, | 845 | 0 | _ => unreachable!(), | 846 | | }; | 847 | | | 848 | 48 | unsafe { slice::from_raw_parts_mut(memory.0, memory.1) } | 849 | | } | 850 | 0 | JitInner::Poisoned => unreachable!(), | 851 | | }; | 852 | | | 853 | 49 | let start = usize::try_from(offset).map_err(|_| OutOfBoundsError)?0 ; | 854 | 49 | let end = start.checked_add(value.len()).ok_or(OutOfBoundsError)?0 ; | 855 | | | 856 | 49 | if end > memory_slice.len() { | 857 | 0 | return Err(OutOfBoundsError); | 858 | 49 | } | 859 | | | 860 | 49 | if !value.is_empty() { | 861 | 49 | memory_slice[start..end].copy_from_slice(value); | 862 | 49 | }0 | 863 | | | 864 | 49 | Ok(()) | 865 | 49 | } |
_RNvMs5_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB5_3Jit12write_memory Line | Count | Source | 837 | 4.15k | pub fn write_memory(&mut self, offset: u32, value: &[u8]) -> Result<(), OutOfBoundsError> { | 838 | 4.15k | let memory_slice = match &mut self.inner { | 839 | 0 | JitInner::NotStarted { store, .. } | JitInner::Done(store) => { | 840 | 0 | self.memory.data_mut(store) | 841 | | } | 842 | | JitInner::Executing(_) => { | 843 | 4.15k | let memory = match *self.shared.try_lock().unwrap() { | 844 | 4.15k | Shared::WithinFunctionCall { memory, .. } => memory, | 845 | 0 | _ => unreachable!(), | 846 | | }; | 847 | | | 848 | 4.15k | unsafe { slice::from_raw_parts_mut(memory.0, memory.1) } | 849 | | } | 850 | 0 | JitInner::Poisoned => unreachable!(), | 851 | | }; | 852 | | | 853 | 4.15k | let start = usize::try_from(offset).map_err(|_| OutOfBoundsError)?0 ; | 854 | 4.15k | let end = start.checked_add(value.len()).ok_or(OutOfBoundsError)?0 ; | 855 | | | 856 | 4.15k | if end > memory_slice.len() { | 857 | 0 | return Err(OutOfBoundsError); | 858 | 4.15k | } | 859 | | | 860 | 4.15k | if !value.is_empty() { | 861 | 4.15k | memory_slice[start..end].copy_from_slice(value); | 862 | 4.15k | }0 | 863 | | | 864 | 4.15k | Ok(()) | 865 | 4.15k | } |
|
866 | | |
867 | | /// See [`super::VirtualMachine::grow_memory`]. |
868 | 9 | pub fn grow_memory(&mut self, additional: HeapPages) -> Result<(), OutOfBoundsError> { |
869 | 9 | let additional = u64::from(u32::from(additional)); |
870 | | |
871 | 9 | match &mut self.inner { |
872 | 2 | JitInner::NotStarted { store, .. } | JitInner::Done(store0 ) => { |
873 | | // This is the simple case: we still have access to the `store` and can perform |
874 | | // the growth synchronously. |
875 | 2 | self.memory |
876 | 2 | .grow(store, additional) |
877 | 2 | .map_err(|_| OutOfBoundsError)?1 ; |
878 | | } |
879 | 0 | JitInner::Poisoned => unreachable!(), |
880 | 7 | JitInner::Executing(function_call) => { |
881 | | // This is the complicated case: the call is in progress and we don't have access |
882 | | // to the `store`. Switch `Shared` to `MemoryGrowRequired`, then resume execution |
883 | | // so that the function handler performs the grow. |
884 | 7 | let mut shared_lock = self.shared.try_lock().unwrap(); |
885 | 7 | match mem::replace(&mut *shared_lock, Shared::Poisoned) { |
886 | | Shared::WithinFunctionCall { |
887 | 7 | memory, |
888 | 7 | expected_return_ty, |
889 | 7 | in_interrupted_waker, |
890 | | } => { |
891 | | // We check now what the memory bounds are, as it is more difficult to |
892 | | // recover from `grow` returning an error than checking manually. |
893 | 7 | let current_pages = if memory.1 == 0 { |
894 | 0 | 0 |
895 | | } else { |
896 | 7 | 1 + u64::try_from((memory.1 - 1) / (64 * 1024)).unwrap() |
897 | | }; |
898 | 7 | if self |
899 | 7 | .memory_type |
900 | 7 | .maximum() |
901 | 7 | .map_or(false, |max| current_pages + additional1 > max1 ) _RNCNvMs5_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB7_3Jit11grow_memorys_0Bd_ Line | Count | Source | 901 | 1 | .map_or(false, |max| current_pages + additional > max) |
Unexecuted instantiation: _RNCNvMs5_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB7_3Jit11grow_memorys_0Bd_ |
902 | | { |
903 | | // Put everything back as it was. |
904 | 1 | *shared_lock = Shared::WithinFunctionCall { |
905 | 1 | memory, |
906 | 1 | expected_return_ty, |
907 | 1 | in_interrupted_waker, |
908 | 1 | }; |
909 | 1 | return Err(OutOfBoundsError); |
910 | 6 | } |
911 | | |
912 | 6 | if let Some(waker) = in_interrupted_waker { |
913 | 6 | waker.wake(); |
914 | 6 | }0 |
915 | | |
916 | 6 | *shared_lock = Shared::MemoryGrowRequired { |
917 | 6 | memory: self.memory, |
918 | 6 | additional, |
919 | 6 | } |
920 | | } |
921 | 0 | _ => unreachable!(), |
922 | | } |
923 | 6 | drop(shared_lock); |
924 | | |
925 | | // Resume the coroutine execution once for the function handler to pick up the |
926 | | // `MemoryGrowRequired`, perform the grow, and switch back to `WithinFunctionCall`. |
927 | | // The `Future` is polled with a no-op waker. We are in total control of when the |
928 | | // execution might be able to progress, hence the lack of need for a waker. |
929 | 6 | match Future::poll( |
930 | 6 | function_call.as_mut(), |
931 | 6 | &mut task::Context::from_waker(task::Waker::noop()), |
932 | 6 | ) { |
933 | 0 | task::Poll::Ready(_) => unreachable!(), |
934 | | task::Poll::Pending => { |
935 | 6 | debug_assert!(matches!0 ( |
936 | 6 | *self.shared.try_lock().unwrap(), |
937 | | Shared::WithinFunctionCall { .. } |
938 | | )); |
939 | | } |
940 | | } |
941 | | } |
942 | | } |
943 | | |
944 | 7 | Ok(()) |
945 | 9 | } _RNvMs5_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB5_3Jit11grow_memory Line | Count | Source | 868 | 3 | pub fn grow_memory(&mut self, additional: HeapPages) -> Result<(), OutOfBoundsError> { | 869 | 3 | let additional = u64::from(u32::from(additional)); | 870 | | | 871 | 3 | match &mut self.inner { | 872 | 2 | JitInner::NotStarted { store, .. } | JitInner::Done(store0 ) => { | 873 | | // This is the simple case: we still have access to the `store` and can perform | 874 | | // the growth synchronously. | 875 | 2 | self.memory | 876 | 2 | .grow(store, additional) | 877 | 2 | .map_err(|_| OutOfBoundsError)?1 ; | 878 | | } | 879 | 0 | JitInner::Poisoned => unreachable!(), | 880 | 1 | JitInner::Executing(function_call) => { | 881 | | // This is the complicated case: the call is in progress and we don't have access | 882 | | // to the `store`. Switch `Shared` to `MemoryGrowRequired`, then resume execution | 883 | | // so that the function handler performs the grow. | 884 | 1 | let mut shared_lock = self.shared.try_lock().unwrap(); | 885 | 1 | match mem::replace(&mut *shared_lock, Shared::Poisoned) { | 886 | | Shared::WithinFunctionCall { | 887 | 1 | memory, | 888 | 1 | expected_return_ty, | 889 | 1 | in_interrupted_waker, | 890 | | } => { | 891 | | // We check now what the memory bounds are, as it is more difficult to | 892 | | // recover from `grow` returning an error than checking manually. | 893 | 1 | let current_pages = if memory.1 == 0 { | 894 | 0 | 0 | 895 | | } else { | 896 | 1 | 1 + u64::try_from((memory.1 - 1) / (64 * 1024)).unwrap() | 897 | | }; | 898 | 1 | if self | 899 | 1 | .memory_type | 900 | 1 | .maximum() | 901 | 1 | .map_or(false, |max| current_pages + additional > max) | 902 | | { | 903 | | // Put everything back as it was. | 904 | 1 | *shared_lock = Shared::WithinFunctionCall { | 905 | 1 | memory, | 906 | 1 | expected_return_ty, | 907 | 1 | in_interrupted_waker, | 908 | 1 | }; | 909 | 1 | return Err(OutOfBoundsError); | 910 | 0 | } | 911 | | | 912 | 0 | if let Some(waker) = in_interrupted_waker { | 913 | 0 | waker.wake(); | 914 | 0 | } | 915 | | | 916 | 0 | *shared_lock = Shared::MemoryGrowRequired { | 917 | 0 | memory: self.memory, | 918 | 0 | additional, | 919 | 0 | } | 920 | | } | 921 | 0 | _ => unreachable!(), | 922 | | } | 923 | 0 | drop(shared_lock); | 924 | | | 925 | | // Resume the coroutine execution once for the function handler to pick up the | 926 | | // `MemoryGrowRequired`, perform the grow, and switch back to `WithinFunctionCall`. | 927 | | // The `Future` is polled with a no-op waker. We are in total control of when the | 928 | | // execution might be able to progress, hence the lack of need for a waker. | 929 | 0 | match Future::poll( | 930 | 0 | function_call.as_mut(), | 931 | 0 | &mut task::Context::from_waker(task::Waker::noop()), | 932 | 0 | ) { | 933 | 0 | task::Poll::Ready(_) => unreachable!(), | 934 | | task::Poll::Pending => { | 935 | 0 | debug_assert!(matches!( | 936 | 0 | *self.shared.try_lock().unwrap(), | 937 | | Shared::WithinFunctionCall { .. } | 938 | | )); | 939 | | } | 940 | | } | 941 | | } | 942 | | } | 943 | | | 944 | 1 | Ok(()) | 945 | 3 | } |
_RNvMs5_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB5_3Jit11grow_memory Line | Count | Source | 868 | 6 | pub fn grow_memory(&mut self, additional: HeapPages) -> Result<(), OutOfBoundsError> { | 869 | 6 | let additional = u64::from(u32::from(additional)); | 870 | | | 871 | 6 | match &mut self.inner { | 872 | 0 | JitInner::NotStarted { store, .. } | JitInner::Done(store) => { | 873 | | // This is the simple case: we still have access to the `store` and can perform | 874 | | // the growth synchronously. | 875 | 0 | self.memory | 876 | 0 | .grow(store, additional) | 877 | 0 | .map_err(|_| OutOfBoundsError)?; | 878 | | } | 879 | 0 | JitInner::Poisoned => unreachable!(), | 880 | 6 | JitInner::Executing(function_call) => { | 881 | | // This is the complicated case: the call is in progress and we don't have access | 882 | | // to the `store`. Switch `Shared` to `MemoryGrowRequired`, then resume execution | 883 | | // so that the function handler performs the grow. | 884 | 6 | let mut shared_lock = self.shared.try_lock().unwrap(); | 885 | 6 | match mem::replace(&mut *shared_lock, Shared::Poisoned) { | 886 | | Shared::WithinFunctionCall { | 887 | 6 | memory, | 888 | 6 | expected_return_ty, | 889 | 6 | in_interrupted_waker, | 890 | | } => { | 891 | | // We check now what the memory bounds are, as it is more difficult to | 892 | | // recover from `grow` returning an error than checking manually. | 893 | 6 | let current_pages = if memory.1 == 0 { | 894 | 0 | 0 | 895 | | } else { | 896 | 6 | 1 + u64::try_from((memory.1 - 1) / (64 * 1024)).unwrap() | 897 | | }; | 898 | 6 | if self | 899 | 6 | .memory_type | 900 | 6 | .maximum() | 901 | 6 | .map_or(false, |max| current_pages + additional > max) | 902 | | { | 903 | | // Put everything back as it was. | 904 | 0 | *shared_lock = Shared::WithinFunctionCall { | 905 | 0 | memory, | 906 | 0 | expected_return_ty, | 907 | 0 | in_interrupted_waker, | 908 | 0 | }; | 909 | 0 | return Err(OutOfBoundsError); | 910 | 6 | } | 911 | | | 912 | 6 | if let Some(waker) = in_interrupted_waker { | 913 | 6 | waker.wake(); | 914 | 6 | }0 | 915 | | | 916 | 6 | *shared_lock = Shared::MemoryGrowRequired { | 917 | 6 | memory: self.memory, | 918 | 6 | additional, | 919 | 6 | } | 920 | | } | 921 | 0 | _ => unreachable!(), | 922 | | } | 923 | 6 | drop(shared_lock); | 924 | | | 925 | | // Resume the coroutine execution once for the function handler to pick up the | 926 | | // `MemoryGrowRequired`, perform the grow, and switch back to `WithinFunctionCall`. | 927 | | // The `Future` is polled with a no-op waker. We are in total control of when the | 928 | | // execution might be able to progress, hence the lack of need for a waker. | 929 | 6 | match Future::poll( | 930 | 6 | function_call.as_mut(), | 931 | 6 | &mut task::Context::from_waker(task::Waker::noop()), | 932 | 6 | ) { | 933 | 0 | task::Poll::Ready(_) => unreachable!(), | 934 | | task::Poll::Pending => { | 935 | 6 | debug_assert!(matches!0 ( | 936 | 6 | *self.shared.try_lock().unwrap(), | 937 | | Shared::WithinFunctionCall { .. } | 938 | | )); | 939 | | } | 940 | | } | 941 | | } | 942 | | } | 943 | | | 944 | 6 | Ok(()) | 945 | 6 | } |
|
946 | | |
947 | | /// See [`super::VirtualMachine::into_prototype`]. |
948 | 7 | pub fn into_prototype(self) -> JitPrototype { |
949 | | // Since the creation has succeeded before, there's no reason why it would fail now. |
950 | 7 | JitPrototype::from_base_components(self.base_components).unwrap() |
951 | 7 | } _RNvMs5_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB5_3Jit14into_prototype Line | Count | Source | 948 | 7 | pub fn into_prototype(self) -> JitPrototype { | 949 | | // Since the creation has succeeded before, there's no reason why it would fail now. | 950 | 7 | JitPrototype::from_base_components(self.base_components).unwrap() | 951 | 7 | } |
Unexecuted instantiation: _RNvMs5_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB5_3Jit14into_prototype |
952 | | } |
953 | | |
954 | | impl fmt::Debug for Jit { |
955 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
956 | 0 | f.debug_tuple("Jit").finish() |
957 | 0 | } Unexecuted instantiation: _RNvXs6_NtNtNtCsjlkOsLH0Zfj_7smoldot8executor2vm3jitNtB5_3JitNtNtCs1p5UDGgVI4d_4core3fmt5Debug3fmt Unexecuted instantiation: _RNvXs6_NtNtNtCsc1ywvx6YAnK_7smoldot8executor2vm3jitNtB5_3JitNtNtCs1p5UDGgVI4d_4core3fmt5Debug3fmt |
958 | | } |
959 | | |
960 | | // Because `BoxFuture` doesn't implement `Sync`, `Jit` also doesn't implement `Sync`. In |
961 | | // reality, however, none of the `&self`-accepting functions of `Jit` ever access the |
962 | | // `BoxFuture` and even if they did, there's no `&self`-accepting function on `BoxFuture` itself |
963 | | // anyway. |
964 | | // TODO: maybe find a way to remove this unsafe implementation |
965 | | unsafe impl Sync for Jit {} |