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