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