/src/wasmtime/crates/wast/src/wast.rs
Line | Count | Source |
1 | | #[cfg(feature = "component-model")] |
2 | | use crate::component; |
3 | | use crate::core; |
4 | | use crate::spectest::*; |
5 | | use json_from_wast::{Action, Command, Const, WasmFile, WasmFileType}; |
6 | | use std::collections::HashMap; |
7 | | use std::path::{Path, PathBuf}; |
8 | | use std::str; |
9 | | use std::sync::Arc; |
10 | | use std::thread; |
11 | | use wasmtime::{error::Context as _, *}; |
12 | | use wast::lexer::Lexer; |
13 | | use wast::parser::{self, ParseBuffer}; |
14 | | |
15 | | /// The wast test script language allows modules to be defined and actions |
16 | | /// to be performed on them. |
17 | | pub struct WastContext { |
18 | | /// Wast files have a concept of a "current" module, which is the most |
19 | | /// recently defined. |
20 | | current: Option<InstanceKind>, |
21 | | core_linker: Linker<()>, |
22 | | modules: HashMap<Option<String>, ModuleKind>, |
23 | | #[cfg(feature = "component-model")] |
24 | | component_linker: component::Linker<()>, |
25 | | |
26 | | /// The store used for core wasm tests/primitives. |
27 | | /// |
28 | | /// Note that components each get their own store so this is not used for |
29 | | /// component-model testing. |
30 | | pub(crate) core_store: Store<()>, |
31 | | pub(crate) async_runtime: Option<tokio::runtime::Runtime>, |
32 | | generate_dwarf: bool, |
33 | | precompile_save: Option<PathBuf>, |
34 | | precompile_load: Option<PathBuf>, |
35 | | |
36 | | modules_by_filename: Arc<HashMap<String, Vec<u8>>>, |
37 | | configure_store: Arc<dyn Fn(&mut Store<()>) + Send + Sync>, |
38 | | ignore_error_messages: bool, |
39 | | } |
40 | | |
41 | | enum Outcome<T = Results> { |
42 | | Ok(T), |
43 | | Trap(Error), |
44 | | } |
45 | | |
46 | | impl<T> Outcome<T> { |
47 | 539 | fn map<U>(self, map: impl FnOnce(T) -> U) -> Outcome<U> { |
48 | 539 | match self { |
49 | 0 | Outcome::Ok(t) => Outcome::Ok(map(t)), |
50 | 539 | Outcome::Trap(t) => Outcome::Trap(t), |
51 | | } |
52 | 539 | } <wasmtime_wast::wast::Outcome<wasmtime::runtime::instance::Instance>>::map::<wasmtime_wast::wast::Results, <wasmtime_wast::wast::WastContext>::run_directive::{closure#3}>Line | Count | Source | 47 | 275 | fn map<U>(self, map: impl FnOnce(T) -> U) -> Outcome<U> { | 48 | 275 | match self { | 49 | 0 | Outcome::Ok(t) => Outcome::Ok(map(t)), | 50 | 275 | Outcome::Trap(t) => Outcome::Trap(t), | 51 | | } | 52 | 275 | } |
<wasmtime_wast::wast::Outcome<(wasmtime::runtime::component::component::Component, wasmtime::runtime::store::Store<()>, wasmtime::runtime::component::instance::Instance)>>::map::<wasmtime_wast::wast::Results, <wasmtime_wast::wast::WastContext>::run_directive::{closure#4}>Line | Count | Source | 47 | 264 | fn map<U>(self, map: impl FnOnce(T) -> U) -> Outcome<U> { | 48 | 264 | match self { | 49 | 0 | Outcome::Ok(t) => Outcome::Ok(map(t)), | 50 | 264 | Outcome::Trap(t) => Outcome::Trap(t), | 51 | | } | 52 | 264 | } |
|
53 | | |
54 | 336k | fn into_result(self) -> Result<T> { |
55 | 336k | match self { |
56 | 336k | Outcome::Ok(t) => Ok(t), |
57 | 0 | Outcome::Trap(t) => Err(t), |
58 | | } |
59 | 336k | } |
60 | | } |
61 | | |
62 | | #[derive(Debug)] |
63 | | enum Results { |
64 | | Core(Vec<Val>), |
65 | | #[cfg(feature = "component-model")] |
66 | | Component(Vec<component::Val>), |
67 | | } |
68 | | |
69 | | #[derive(Clone)] |
70 | | enum ModuleKind { |
71 | | Core(Module), |
72 | | #[cfg(feature = "component-model")] |
73 | | Component(component::Component), |
74 | | } |
75 | | |
76 | | enum InstanceKind { |
77 | | Core(Instance), |
78 | | #[cfg(feature = "component-model")] |
79 | | Component(Store<()>, component::Instance), |
80 | | } |
81 | | |
82 | | enum Export<'a> { |
83 | | Core(Extern), |
84 | | #[cfg(feature = "component-model")] |
85 | | Component(&'a mut Store<()>, component::Func), |
86 | | |
87 | | /// Impossible-to-construct variant to consider `'a` used when the |
88 | | /// `component-model` feature is disabled. |
89 | | _Unused(std::convert::Infallible, &'a ()), |
90 | | } |
91 | | |
92 | | /// Whether or not to use async APIs when calling wasm during wast testing. |
93 | | /// |
94 | | /// Passed to [`WastContext::new`]. |
95 | | #[derive(Debug, Copy, Clone, PartialEq)] |
96 | | #[expect(missing_docs, reason = "self-describing variants")] |
97 | | pub enum Async { |
98 | | Yes, |
99 | | No, |
100 | | } |
101 | | |
102 | | impl WastContext { |
103 | | /// Construct a new instance of `WastContext`. |
104 | | /// |
105 | | /// The `engine` provided is used for all store/module/component creation |
106 | | /// and should be appropriately configured by the caller. The `async_` |
107 | | /// configuration indicates whether functions are invoked either async or |
108 | | /// sync, and then the `configure` callback is used whenever a store is |
109 | | /// created to further configure its settings. |
110 | 3.83k | pub fn new( |
111 | 3.83k | engine: &Engine, |
112 | 3.83k | async_: Async, |
113 | 3.83k | configure: impl Fn(&mut Store<()>) + Send + Sync + 'static, |
114 | 3.83k | ) -> Self { |
115 | | // Spec tests will redefine the same module/name sometimes, so we need |
116 | | // to allow shadowing in the linker which picks the most recent |
117 | | // definition as what to link when linking. |
118 | 3.83k | let mut core_linker = Linker::new(engine); |
119 | 3.83k | core_linker.allow_shadowing(true); |
120 | | Self { |
121 | 3.83k | current: None, |
122 | 3.83k | core_linker, |
123 | | #[cfg(feature = "component-model")] |
124 | | component_linker: { |
125 | 3.83k | let mut linker = component::Linker::new(engine); |
126 | 3.83k | linker.allow_shadowing(true); |
127 | 3.83k | linker |
128 | | }, |
129 | | core_store: { |
130 | 3.83k | let mut store = Store::new(engine, ()); |
131 | 3.83k | configure(&mut store); |
132 | 3.83k | store |
133 | | }, |
134 | 3.83k | modules: Default::default(), |
135 | 3.83k | async_runtime: if async_ == Async::Yes { |
136 | 2.46k | Some( |
137 | 2.46k | tokio::runtime::Builder::new_current_thread() |
138 | 2.46k | .build() |
139 | 2.46k | .unwrap(), |
140 | 2.46k | ) |
141 | | } else { |
142 | 1.37k | None |
143 | | }, |
144 | | generate_dwarf: true, |
145 | 3.83k | precompile_save: None, |
146 | 3.83k | precompile_load: None, |
147 | 3.83k | modules_by_filename: Arc::default(), |
148 | 3.83k | configure_store: Arc::new(configure), |
149 | | ignore_error_messages: false, |
150 | | } |
151 | 3.83k | } <wasmtime_wast::wast::WastContext>::new::<wasmtime_fuzzing::oracles::wast_test::{closure#2}>Line | Count | Source | 110 | 3.83k | pub fn new( | 111 | 3.83k | engine: &Engine, | 112 | 3.83k | async_: Async, | 113 | 3.83k | configure: impl Fn(&mut Store<()>) + Send + Sync + 'static, | 114 | 3.83k | ) -> Self { | 115 | | // Spec tests will redefine the same module/name sometimes, so we need | 116 | | // to allow shadowing in the linker which picks the most recent | 117 | | // definition as what to link when linking. | 118 | 3.83k | let mut core_linker = Linker::new(engine); | 119 | 3.83k | core_linker.allow_shadowing(true); | 120 | | Self { | 121 | 3.83k | current: None, | 122 | 3.83k | core_linker, | 123 | | #[cfg(feature = "component-model")] | 124 | | component_linker: { | 125 | 3.83k | let mut linker = component::Linker::new(engine); | 126 | 3.83k | linker.allow_shadowing(true); | 127 | 3.83k | linker | 128 | | }, | 129 | | core_store: { | 130 | 3.83k | let mut store = Store::new(engine, ()); | 131 | 3.83k | configure(&mut store); | 132 | 3.83k | store | 133 | | }, | 134 | 3.83k | modules: Default::default(), | 135 | 3.83k | async_runtime: if async_ == Async::Yes { | 136 | 2.46k | Some( | 137 | 2.46k | tokio::runtime::Builder::new_current_thread() | 138 | 2.46k | .build() | 139 | 2.46k | .unwrap(), | 140 | 2.46k | ) | 141 | | } else { | 142 | 1.37k | None | 143 | | }, | 144 | | generate_dwarf: true, | 145 | 3.83k | precompile_save: None, | 146 | 3.83k | precompile_load: None, | 147 | 3.83k | modules_by_filename: Arc::default(), | 148 | 3.83k | configure_store: Arc::new(configure), | 149 | | ignore_error_messages: false, | 150 | | } | 151 | 3.83k | } |
Unexecuted instantiation: <wasmtime_wast::wast::WastContext>::new::<_> |
152 | | |
153 | 49.8k | fn engine(&self) -> &Engine { |
154 | 49.8k | self.core_linker.engine() |
155 | 49.8k | } |
156 | | |
157 | | /// Configures whether or not error messages are ignored in directives like |
158 | | /// `assert_invalid`. |
159 | 3.83k | pub fn ignore_error_messages(&mut self, ignore: bool) -> &mut Self { |
160 | 3.83k | self.ignore_error_messages = ignore; |
161 | 3.83k | self |
162 | 3.83k | } |
163 | | |
164 | | /// Saves precompiled modules/components into `path` instead of executing |
165 | | /// test directives. |
166 | 0 | pub fn precompile_save(&mut self, path: impl AsRef<Path>) -> &mut Self { |
167 | 0 | self.precompile_save = Some(path.as_ref().into()); |
168 | 0 | self |
169 | 0 | } |
170 | | |
171 | | /// Loads precompiled modules/components from `path` instead of compiling |
172 | | /// natively. |
173 | 0 | pub fn precompile_load(&mut self, path: impl AsRef<Path>) -> &mut Self { |
174 | 0 | self.precompile_load = Some(path.as_ref().into()); |
175 | 0 | self |
176 | 0 | } |
177 | | |
178 | 379k | fn get_export(&mut self, module: Option<&str>, name: &str) -> Result<Export<'_>> { |
179 | 379k | if let Some(module) = module { |
180 | | return Ok(Export::Core( |
181 | 1.39k | self.core_linker |
182 | 1.39k | .get(&mut self.core_store, module, name) |
183 | 1.39k | .with_context(|| format_err!("no item named `{module}::{name}` found"))?, |
184 | | )); |
185 | 377k | } |
186 | | |
187 | 377k | let cur = self |
188 | 377k | .current |
189 | 377k | .as_mut() |
190 | 377k | .ok_or_else(|| format_err!("no previous instance found"))?; |
191 | 377k | Ok(match cur { |
192 | 373k | InstanceKind::Core(i) => Export::Core( |
193 | 373k | i.get_export(&mut self.core_store, name) |
194 | 373k | .ok_or_else(|| format_err!("no item named `{name}` found"))?, |
195 | | ), |
196 | | #[cfg(feature = "component-model")] |
197 | 4.43k | InstanceKind::Component(store, i) => { |
198 | 4.43k | let export = i |
199 | 4.43k | .get_func(&mut *store, name) |
200 | 4.43k | .ok_or_else(|| format_err!("no func named `{name}` found"))?; |
201 | 4.43k | Export::Component(store, export) |
202 | | } |
203 | | }) |
204 | 379k | } |
205 | | |
206 | 17.5k | fn instantiate_module(&mut self, module: &Module) -> Result<Outcome<Instance>> { |
207 | 17.5k | let instance = match &self.async_runtime { |
208 | 7.57k | Some(rt) => rt.block_on( |
209 | 7.57k | self.core_linker |
210 | 7.57k | .instantiate_async(&mut self.core_store, &module), |
211 | | ), |
212 | 10.0k | None => self.core_linker.instantiate(&mut self.core_store, &module), |
213 | | }; |
214 | 17.5k | Ok(match instance { |
215 | 16.1k | Ok(i) => Outcome::Ok(i), |
216 | 1.42k | Err(e) => Outcome::Trap(e), |
217 | | }) |
218 | 17.5k | } |
219 | | |
220 | | #[cfg(feature = "component-model")] |
221 | 5.97k | fn instantiate_component( |
222 | 5.97k | &mut self, |
223 | 5.97k | component: &component::Component, |
224 | 5.97k | ) -> Result<Outcome<(component::Component, Store<()>, component::Instance)>> { |
225 | 5.97k | let mut store = Store::new(self.engine(), ()); |
226 | 5.97k | (self.configure_store)(&mut store); |
227 | 5.97k | let instance = match &self.async_runtime { |
228 | 4.47k | Some(rt) => rt.block_on( |
229 | 4.47k | self.component_linker |
230 | 4.47k | .instantiate_async(&mut store, &component), |
231 | | ), |
232 | 1.49k | None => self.component_linker.instantiate(&mut store, &component), |
233 | | }; |
234 | 5.97k | Ok(match instance { |
235 | 5.11k | Ok(i) => Outcome::Ok((component.clone(), store, i)), |
236 | 857 | Err(e) => Outcome::Trap(e), |
237 | | }) |
238 | 5.97k | } |
239 | | |
240 | | /// Register "spectest" which is used by the spec testsuite. |
241 | 3.83k | pub fn register_spectest(&mut self, config: &SpectestConfig) -> Result<()> { |
242 | 3.83k | link_spectest(&mut self.core_linker, &mut self.core_store, config)?; |
243 | | #[cfg(feature = "component-model")] |
244 | 3.83k | link_component_spectest(&mut self.component_linker)?; |
245 | 3.83k | Ok(()) |
246 | 3.83k | } |
247 | | |
248 | | /// Register the "wasmtime" module, which provides utilities that our misc |
249 | | /// tests use. |
250 | 3.83k | pub fn register_wasmtime(&mut self) -> Result<()> { |
251 | 3.83k | self.core_linker |
252 | 3.83k | .func_wrap("wasmtime", "gc", |mut caller: Caller<_>| { |
253 | 2.88k | caller.gc(None)?; |
254 | 2.88k | Ok(()) |
255 | 2.88k | })?; |
256 | | #[cfg(feature = "component-model")] |
257 | | { |
258 | 3.83k | let mut i = self.component_linker.instance("wasmtime")?; |
259 | 3.83k | i.func_wrap( |
260 | 3.83k | "set-max-table-capacity", |
261 | 1 | |mut store, (capacity,): (u32,)| { |
262 | 1 | store |
263 | 1 | .as_context_mut() |
264 | 1 | .concurrent_resource_table() |
265 | 1 | .expect("table must be present") |
266 | 1 | .set_max_capacity(capacity.try_into().unwrap()); |
267 | 1 | Ok(()) |
268 | 1 | }, |
269 | 0 | )?; |
270 | | } |
271 | 3.83k | Ok(()) |
272 | 3.83k | } |
273 | | |
274 | | /// Perform the action portion of a command. |
275 | 379k | fn perform_action(&mut self, action: &Action<'_>) -> Result<Outcome> { |
276 | | // Need to simultaneously borrow `self.async_runtime` and a `&mut |
277 | | // Store` from components so work around the borrow checker issues by |
278 | | // taking out the async runtime here and putting it back through a |
279 | | // destructor. |
280 | | struct ReplaceRuntime<'a> { |
281 | | ctx: &'a mut WastContext, |
282 | | rt: Option<tokio::runtime::Runtime>, |
283 | | } |
284 | | impl Drop for ReplaceRuntime<'_> { |
285 | 379k | fn drop(&mut self) { |
286 | 379k | self.ctx.async_runtime = self.rt.take(); |
287 | 379k | } |
288 | | } |
289 | 379k | let replace = ReplaceRuntime { |
290 | 379k | rt: self.async_runtime.take(), |
291 | 379k | ctx: self, |
292 | 379k | }; |
293 | 379k | let me = &mut *replace.ctx; |
294 | 379k | match action { |
295 | | Action::Invoke { |
296 | 378k | module, |
297 | 378k | field, |
298 | 378k | args, |
299 | 378k | } => match me.get_export(module.as_deref(), field)? { |
300 | 374k | Export::Core(export) => { |
301 | 374k | drop(replace); |
302 | 374k | let func = export |
303 | 374k | .into_func() |
304 | 374k | .ok_or_else(|| format_err!("no function named `{field}`"))?; |
305 | 374k | let values = args |
306 | 374k | .iter() |
307 | 521k | .map(|v| match v { |
308 | 521k | Const::Core(v) => core::val(self, v), |
309 | 0 | _ => bail!("expected core function, found other other argument {v:?}"), |
310 | 521k | }) |
311 | 374k | .collect::<Result<Vec<_>>>()?; |
312 | | |
313 | 374k | let mut results = |
314 | 374k | vec![Val::null_func_ref(); func.ty(&self.core_store).results().len()]; |
315 | 374k | let result = match &self.async_runtime { |
316 | 160k | Some(rt) => rt.block_on(func.call_async( |
317 | 160k | &mut self.core_store, |
318 | 160k | &values, |
319 | 160k | &mut results, |
320 | | )), |
321 | 213k | None => func.call(&mut self.core_store, &values, &mut results), |
322 | | }; |
323 | | |
324 | 374k | Ok(match result { |
325 | 335k | Ok(()) => Outcome::Ok(Results::Core(results)), |
326 | 38.9k | Err(e) => Outcome::Trap(e), |
327 | | }) |
328 | | } |
329 | | #[cfg(feature = "component-model")] |
330 | 4.43k | Export::Component(store, func) => { |
331 | 4.43k | let values = args |
332 | 4.43k | .iter() |
333 | 4.43k | .map(|v| match v { |
334 | 782 | Const::Component(v) => component::val(v), |
335 | 0 | _ => bail!("expected component function, found other argument {v:?}"), |
336 | 782 | }) |
337 | 4.43k | .collect::<Result<Vec<_>>>()?; |
338 | | |
339 | 4.43k | let mut results = |
340 | 4.43k | vec![component::Val::Bool(false); func.ty(&store).results().len()]; |
341 | 4.43k | let result = match &replace.rt { |
342 | 4.24k | Some(rt) => { |
343 | 4.24k | rt.block_on(func.call_async(&mut *store, &values, &mut results)) |
344 | | } |
345 | 194 | None => func.call(&mut *store, &values, &mut results), |
346 | | }; |
347 | 4.43k | Ok(match result { |
348 | 3.17k | Ok(()) => Outcome::Ok(Results::Component(results)), |
349 | 1.25k | Err(e) => Outcome::Trap(e), |
350 | | }) |
351 | | } |
352 | | }, |
353 | 128 | Action::Get { module, field, .. } => me.get(module.as_deref(), field), |
354 | | } |
355 | 379k | } |
356 | | |
357 | | /// Instantiates the `module` provided and registers the instance under the |
358 | | /// `name` provided if successful. |
359 | 23.0k | fn module(&mut self, name: Option<&str>, module: &ModuleKind) -> Result<()> { |
360 | 23.0k | match module { |
361 | 17.3k | ModuleKind::Core(module) => { |
362 | 17.3k | let instance = match self.instantiate_module(&module)? { |
363 | 16.1k | Outcome::Ok(i) => i, |
364 | 1.14k | Outcome::Trap(e) => return Err(e).context("instantiation failed"), |
365 | | }; |
366 | 16.1k | if let Some(name) = name { |
367 | 626 | self.core_linker |
368 | 626 | .instance(&mut self.core_store, name, instance)?; |
369 | 15.5k | } |
370 | 16.1k | self.current = Some(InstanceKind::Core(instance)); |
371 | | } |
372 | | #[cfg(feature = "component-model")] |
373 | 5.70k | ModuleKind::Component(module) => { |
374 | 5.70k | let (component, mut store, instance) = match self.instantiate_component(&module)? { |
375 | 5.11k | Outcome::Ok(i) => i, |
376 | 593 | Outcome::Trap(e) => return Err(e).context("instantiation failed"), |
377 | | }; |
378 | 5.11k | if let Some(name) = name { |
379 | 1.81k | let ty = component.component_type(); |
380 | 1.81k | let engine = self.engine().clone(); |
381 | 1.81k | let mut linker = self.component_linker.instance(name)?; |
382 | 21.5k | for (name, item) in ty.exports(&engine) { |
383 | 21.5k | match item.ty { |
384 | | component::types::ComponentItem::Module(_) => { |
385 | 302 | let module = instance.get_module(&mut store, name).unwrap(); |
386 | 302 | linker.module(name, &module)?; |
387 | | } |
388 | | component::types::ComponentItem::Resource(_) => { |
389 | 74 | let resource = instance.get_resource(&mut store, name).unwrap(); |
390 | 74 | linker.resource(name, resource, |_, _| Ok(()))?; |
391 | | } |
392 | | // TODO: should ideally reflect more than just |
393 | | // modules/resources into the linker's namespace |
394 | | // but that's not easily supported today for host |
395 | | // functions due to the inability to take a |
396 | | // function from one instance and put it into the |
397 | | // linker (must go through the host right now). |
398 | 21.2k | _ => {} |
399 | | } |
400 | | } |
401 | 3.30k | } |
402 | 5.11k | self.current = Some(InstanceKind::Component(store, instance)); |
403 | | } |
404 | | } |
405 | 21.2k | Ok(()) |
406 | 23.0k | } |
407 | | |
408 | | /// Compiles the module `wat` into binary and returns the name found within |
409 | | /// it, if any. |
410 | | /// |
411 | | /// This will not register the name within `self.modules`. |
412 | 48.0k | fn module_definition(&mut self, file: &WasmFile) -> Result<ModuleKind> { |
413 | 48.0k | let name = match file.module_type { |
414 | 6.11k | WasmFileType::Text => file |
415 | 6.11k | .binary_filename |
416 | 6.11k | .as_ref() |
417 | 6.11k | .ok_or_else(|| format_err!("cannot compile module that isn't a valid binary"))?, |
418 | 41.9k | WasmFileType::Binary => &file.filename, |
419 | | }; |
420 | | |
421 | 41.9k | match &self.precompile_load { |
422 | 0 | Some(path) => { |
423 | 0 | let cwasm = path.join(&name[..]).with_extension("cwasm"); |
424 | 0 | match Engine::detect_precompiled_file(&cwasm) |
425 | 0 | .with_context(|| format!("failed to read {cwasm:?}"))? |
426 | | { |
427 | | Some(Precompiled::Module) => { |
428 | 0 | let module = unsafe { Module::deserialize_file(self.engine(), &cwasm)? }; |
429 | 0 | Ok(ModuleKind::Core(module)) |
430 | | } |
431 | | #[cfg(feature = "component-model")] |
432 | | Some(Precompiled::Component) => { |
433 | 0 | let component = unsafe { |
434 | 0 | component::Component::deserialize_file(self.engine(), &cwasm)? |
435 | | }; |
436 | 0 | Ok(ModuleKind::Component(component)) |
437 | | } |
438 | | #[cfg(not(feature = "component-model"))] |
439 | | Some(Precompiled::Component) => { |
440 | | bail!("support for components disabled at compile time") |
441 | | } |
442 | 0 | None => bail!("expected a cwasm file"), |
443 | | } |
444 | | } |
445 | | None => { |
446 | 41.9k | let bytes = &self.modules_by_filename[&name[..]]; |
447 | | |
448 | 41.9k | if wasmparser::Parser::is_core_wasm(&bytes) { |
449 | 36.1k | let module = Module::new(self.engine(), &bytes)?; |
450 | 17.5k | Ok(ModuleKind::Core(module)) |
451 | | } else { |
452 | | #[cfg(feature = "component-model")] |
453 | | { |
454 | 5.86k | let component = component::Component::new(self.engine(), &bytes)?; |
455 | 4.85k | Ok(ModuleKind::Component(component)) |
456 | | } |
457 | | #[cfg(not(feature = "component-model"))] |
458 | | bail!("component-model support not enabled"); |
459 | | } |
460 | | } |
461 | | } |
462 | 48.0k | } |
463 | | |
464 | | /// Register an instance to make it available for performing actions. |
465 | 492 | fn register(&mut self, name: Option<&str>, as_name: &str) -> Result<()> { |
466 | 492 | match name { |
467 | 251 | Some(name) => self.core_linker.alias_module(name, as_name), |
468 | | None => { |
469 | 241 | let current = self |
470 | 241 | .current |
471 | 241 | .as_ref() |
472 | 241 | .ok_or(format_err!("no previous instance"))?; |
473 | 241 | match current { |
474 | 241 | InstanceKind::Core(current) => { |
475 | 241 | self.core_linker |
476 | 241 | .instance(&mut self.core_store, as_name, *current)?; |
477 | | } |
478 | | #[cfg(feature = "component-model")] |
479 | | InstanceKind::Component(..) => { |
480 | 0 | bail!("register not implemented for components"); |
481 | | } |
482 | | } |
483 | 241 | Ok(()) |
484 | | } |
485 | | } |
486 | 492 | } |
487 | | |
488 | | /// Get the value of an exported global from an instance. |
489 | 128 | fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result<Outcome> { |
490 | 128 | let global = match self.get_export(instance_name, field)? { |
491 | 128 | Export::Core(e) => e |
492 | 128 | .into_global() |
493 | 128 | .ok_or_else(|| format_err!("no global named `{field}`"))?, |
494 | | #[cfg(feature = "component-model")] |
495 | 0 | Export::Component(..) => bail!("no global named `{field}`"), |
496 | | }; |
497 | 128 | Ok(Outcome::Ok(Results::Core(vec![ |
498 | 128 | global.get(&mut self.core_store), |
499 | 128 | ]))) |
500 | 128 | } |
501 | | |
502 | 336k | fn assert_return(&mut self, result: Outcome, results: &[Const]) -> Result<()> { |
503 | 336k | match result.into_result()? { |
504 | 333k | Results::Core(values) => { |
505 | 333k | if values.len() != results.len() { |
506 | 0 | bail!("expected {} results found {}", results.len(), values.len()); |
507 | 333k | } |
508 | 333k | for (i, (v, e)) in values.iter().zip(results).enumerate() { |
509 | 331k | let e = match e { |
510 | 331k | Const::Core(core) => core, |
511 | 0 | _ => bail!("expected core value found other value {e:?}"), |
512 | | }; |
513 | 331k | core::match_val(&mut self.core_store, v, e) |
514 | 331k | .with_context(|| format!("result {i} didn't match"))?; |
515 | | } |
516 | | } |
517 | | #[cfg(feature = "component-model")] |
518 | 3.16k | Results::Component(values) => { |
519 | 3.16k | if values.len() != results.len() { |
520 | 0 | bail!("expected {} results found {}", results.len(), values.len()); |
521 | 3.16k | } |
522 | 3.16k | for (i, (v, e)) in values.iter().zip(results).enumerate() { |
523 | 2.73k | let e = match e { |
524 | 2.73k | Const::Component(val) => val, |
525 | 0 | _ => bail!("expected component value found other value {e:?}"), |
526 | | }; |
527 | 2.73k | component::match_val(e, v) |
528 | 2.73k | .with_context(|| format!("result {i} didn't match"))?; |
529 | | } |
530 | | } |
531 | | } |
532 | 336k | Ok(()) |
533 | 336k | } |
534 | | |
535 | 40.6k | fn assert_trap(&self, result: Outcome, expected: &str) -> Result<()> { |
536 | 40.6k | let trap = match result { |
537 | 0 | Outcome::Ok(values) => bail!("expected trap, got {values:?}"), |
538 | 40.6k | Outcome::Trap(t) => t, |
539 | | }; |
540 | 40.6k | let actual = format!("{trap:?}"); |
541 | 40.6k | if actual.contains(expected) |
542 | | // `bulk-memory-operations/bulk.wast` checks for a message that |
543 | | // specifies which element is uninitialized, but our traps don't |
544 | | // shepherd that information out. |
545 | 282 | || (expected.contains("uninitialized element 2") && actual.contains("uninitialized element")) |
546 | | // function references call_ref |
547 | 277 | || (expected.contains("null function") && (actual.contains("uninitialized element") || actual.contains("null reference"))) |
548 | | // GC tests say "null $kind reference" but we just say "null reference". |
549 | 272 | || (expected.contains("null") && expected.contains("reference") && actual.contains("null reference")) |
550 | | // upstream component model tests expect slightly different error |
551 | | // messages than we generate. |
552 | 90 | || (expected.contains("cannot write") && actual.contains("cannot write")) |
553 | 36 | || (expected.contains("cannot read") && actual.contains("cannot read")) |
554 | | { |
555 | 40.6k | return Ok(()); |
556 | 0 | } |
557 | 0 | bail!("expected '{expected}', got '{actual}'") |
558 | 40.6k | } |
559 | | |
560 | 125 | fn assert_exception(&mut self, result: Outcome) -> Result<()> { |
561 | 125 | match result { |
562 | 0 | Outcome::Ok(values) => bail!("expected exception, got {values:?}"), |
563 | 125 | Outcome::Trap(err) if err.is::<ThrownException>() => { |
564 | | // Discard the thrown exception. |
565 | 125 | let _ = self |
566 | 125 | .core_store |
567 | 125 | .take_pending_exception() |
568 | 125 | .expect("there should be a pending exception on the store"); |
569 | 125 | Ok(()) |
570 | | } |
571 | 0 | Outcome::Trap(err) => bail!("expected exception, got {err:?}"), |
572 | | } |
573 | 125 | } |
574 | | |
575 | | /// Run a wast script from a byte buffer. |
576 | 3.83k | pub fn run_wast(&mut self, filename: &str, wast: &[u8]) -> Result<()> { |
577 | 3.83k | let wast = str::from_utf8(wast)?; |
578 | | |
579 | 3.83k | let adjust_wast = |mut err: wast::Error| { |
580 | 0 | err.set_path(filename.as_ref()); |
581 | 0 | err.set_text(wast); |
582 | 0 | err |
583 | 0 | }; |
584 | | |
585 | 3.83k | let mut lexer = Lexer::new(wast); |
586 | 3.83k | lexer.allow_confusing_unicode(filename.ends_with("names.wast")); |
587 | 3.83k | let mut buf = ParseBuffer::new_with_lexer(lexer).map_err(adjust_wast)?; |
588 | 3.83k | buf.track_instr_spans(self.generate_dwarf); |
589 | 3.83k | let ast = parser::parse::<wast::Wast>(&buf).map_err(adjust_wast)?; |
590 | | |
591 | 3.83k | let mut ast = json_from_wast::Opts::default() |
592 | 3.83k | .dwarf(self.generate_dwarf) |
593 | 3.83k | .convert(filename, wast, ast) |
594 | 3.83k | .to_wasmtime_result()?; |
595 | | |
596 | | // Clear out any modules, if any, from a previous `*.wast` file being |
597 | | // run, if any. |
598 | 3.83k | if !self.modules_by_filename.is_empty() { |
599 | 0 | self.modules_by_filename = Arc::default(); |
600 | 3.83k | } |
601 | 3.83k | let modules_by_filename = Arc::get_mut(&mut self.modules_by_filename).unwrap(); |
602 | 48.0k | for (name, bytes) in ast.wasms.drain(..) { |
603 | 48.0k | let prev = modules_by_filename.insert(name, bytes); |
604 | 48.0k | assert!(prev.is_none()); |
605 | | } |
606 | | |
607 | 3.83k | match &self.precompile_save { |
608 | 0 | Some(path) => { |
609 | 0 | let json_path = path |
610 | 0 | .join(Path::new(filename).file_name().unwrap()) |
611 | 0 | .with_extension("json"); |
612 | 0 | let json = serde_json::to_string(&ast)?; |
613 | 0 | std::fs::write(&json_path, json) |
614 | 0 | .with_context(|| format!("failed to write {json_path:?}"))?; |
615 | 0 | for (name, bytes) in self.modules_by_filename.iter() { |
616 | 0 | let cwasm_path = path.join(name).with_extension("cwasm"); |
617 | 0 | let cwasm = if wasmparser::Parser::is_core_wasm(&bytes) { |
618 | 0 | self.engine().precompile_module(bytes) |
619 | | } else { |
620 | | #[cfg(feature = "component-model")] |
621 | | { |
622 | 0 | self.engine().precompile_component(bytes) |
623 | | } |
624 | | #[cfg(not(feature = "component-model"))] |
625 | | bail!("component-model support not enabled"); |
626 | | }; |
627 | 0 | if let Ok(cwasm) = cwasm { |
628 | 0 | std::fs::write(&cwasm_path, cwasm) |
629 | 0 | .with_context(|| format!("failed to write {cwasm_path:?}"))?; |
630 | 0 | } |
631 | | } |
632 | 0 | Ok(()) |
633 | | } |
634 | 3.83k | None => self.run_directives(ast.commands, filename), |
635 | | } |
636 | 3.83k | } |
637 | | |
638 | 3.85k | fn run_directives(&mut self, directives: Vec<Command<'_>>, filename: &str) -> Result<()> { |
639 | 3.85k | thread::scope(|scope| { |
640 | 3.85k | let mut threads = HashMap::new(); |
641 | 429k | for directive in directives { |
642 | 429k | let line = directive.line(); |
643 | 429k | log::debug!("running directive on {filename}:{line}"); |
644 | 429k | self.run_directive(directive, filename, &scope, &mut threads) |
645 | 429k | .with_context(|| format!("failed directive on {filename}:{line}"))?; |
646 | | } |
647 | 3.85k | Ok(()) |
648 | 3.85k | }) |
649 | 3.85k | } |
650 | | |
651 | 429k | fn run_directive<'a>( |
652 | 429k | &mut self, |
653 | 429k | directive: Command<'a>, |
654 | 429k | filename: &'a str, |
655 | 429k | // wast: &'a str, |
656 | 429k | scope: &'a thread::Scope<'a, '_>, |
657 | 429k | threads: &mut HashMap<String, thread::ScopedJoinHandle<'a, Result<()>>>, |
658 | 429k | ) -> Result<()> { |
659 | | use Command::*; |
660 | | |
661 | 429k | match directive { |
662 | | Module { |
663 | 19.8k | name, |
664 | 19.8k | file, |
665 | | line: _, |
666 | | } => { |
667 | 19.8k | let module = self.module_definition(&file)?; |
668 | 19.8k | self.module(name.as_deref(), &module)?; |
669 | | } |
670 | | ModuleDefinition { |
671 | 285 | name, |
672 | 285 | file, |
673 | | line: _, |
674 | | } => { |
675 | 285 | let module = self.module_definition(&file)?; |
676 | 285 | self.modules.insert(name.map(|s| s.to_string()), module); |
677 | | } |
678 | | ModuleInstance { |
679 | 1.40k | instance, |
680 | 1.40k | module, |
681 | | line: _, |
682 | | } => { |
683 | 1.40k | let module = self |
684 | 1.40k | .modules |
685 | 1.40k | .get(&module.as_ref().map(|s| s.to_string())) |
686 | 1.40k | .cloned() |
687 | 1.40k | .ok_or_else(|| format_err!("no module named {module:?}"))?; |
688 | 1.40k | self.module(instance.as_deref(), &module)?; |
689 | | } |
690 | 492 | Register { line: _, name, as_ } => { |
691 | 492 | self.register(name.as_deref(), &as_)?; |
692 | | } |
693 | 2.48k | Action { action, line: _ } => { |
694 | 2.48k | self.perform_action(&action)?; |
695 | | } |
696 | | AssertReturn { |
697 | 336k | action, |
698 | 336k | expected, |
699 | | line: _, |
700 | | } => { |
701 | 336k | let result = self.perform_action(&action)?; |
702 | 336k | self.assert_return(result, &expected)?; |
703 | | } |
704 | | AssertTrap { |
705 | 39.9k | action, |
706 | 39.9k | text, |
707 | | line: _, |
708 | | } => { |
709 | 39.9k | let result = self.perform_action(&action)?; |
710 | 39.9k | self.assert_trap(result, &text)?; |
711 | | } |
712 | | AssertUninstantiable { |
713 | 539 | file, |
714 | 539 | text, |
715 | | line: _, |
716 | | } => { |
717 | 539 | let result = match self.module_definition(&file)? { |
718 | 275 | ModuleKind::Core(module) => self |
719 | 275 | .instantiate_module(&module)? |
720 | 275 | .map(|_| Results::Core(Vec::new())), |
721 | | #[cfg(feature = "component-model")] |
722 | 264 | ModuleKind::Component(component) => self |
723 | 264 | .instantiate_component(&component)? |
724 | 264 | .map(|_| Results::Component(Vec::new())), |
725 | | }; |
726 | 539 | self.assert_trap(result, &text)?; |
727 | | } |
728 | | AssertExhaustion { |
729 | 199 | action, |
730 | 199 | text, |
731 | | line: _, |
732 | | } => { |
733 | 199 | let result = self.perform_action(&action)?; |
734 | 199 | self.assert_trap(result, &text)?; |
735 | | } |
736 | | AssertInvalid { |
737 | 16.5k | file, |
738 | 16.5k | text, |
739 | | line: _, |
740 | | } => { |
741 | 16.5k | let err = match self.module_definition(&file) { |
742 | 0 | Ok(_) => bail!("expected module to fail to build"), |
743 | 16.5k | Err(e) => e, |
744 | | }; |
745 | 16.5k | self.match_error_message(&text, err)?; |
746 | | } |
747 | | AssertMalformed { |
748 | 9.02k | file, |
749 | | text: _, |
750 | | line: _, |
751 | | } => { |
752 | 9.02k | if let Ok(_) = self.module_definition(&file) { |
753 | 0 | bail!("expected malformed module to fail to instantiate"); |
754 | 9.02k | } |
755 | | } |
756 | | AssertUnlinkable { |
757 | 1.73k | file, |
758 | 1.73k | text, |
759 | | line: _, |
760 | | } => { |
761 | 1.73k | let module = self.module_definition(&file)?; |
762 | 1.73k | let err = match self.module(None, &module) { |
763 | 0 | Ok(_) => bail!("expected module to fail to link"), |
764 | 1.73k | Err(e) => e, |
765 | | }; |
766 | 1.73k | self.match_error_message(&text, err)?; |
767 | | } |
768 | 125 | AssertException { line: _, action } => { |
769 | 125 | let result = self.perform_action(&action)?; |
770 | 125 | self.assert_exception(result)?; |
771 | | } |
772 | | |
773 | | Thread { |
774 | 17 | name, |
775 | 17 | shared_module, |
776 | 17 | commands, |
777 | | line: _, |
778 | | } => { |
779 | 17 | let mut core_linker = Linker::new(self.engine()); |
780 | 17 | if let Some(id) = shared_module { |
781 | 14 | let items = self |
782 | 14 | .core_linker |
783 | 14 | .iter(&mut self.core_store) |
784 | 238 | .filter(|(module, _, _)| *module == &id[..]) |
785 | 14 | .collect::<Vec<_>>(); |
786 | 14 | for (module, name, item) in items { |
787 | 14 | core_linker.define(&mut self.core_store, module, name, item)?; |
788 | | } |
789 | 3 | } |
790 | 17 | let mut child_cx = WastContext { |
791 | 17 | current: None, |
792 | 17 | core_linker, |
793 | | #[cfg(feature = "component-model")] |
794 | 17 | component_linker: component::Linker::new(self.engine()), |
795 | | core_store: { |
796 | 17 | let mut store = Store::new(self.engine(), ()); |
797 | 17 | (self.configure_store)(&mut store); |
798 | 17 | store |
799 | | }, |
800 | 17 | modules: self.modules.clone(), |
801 | 17 | async_runtime: self.async_runtime.as_ref().map(|_| { |
802 | 0 | tokio::runtime::Builder::new_current_thread() |
803 | 0 | .build() |
804 | 0 | .unwrap() |
805 | 0 | }), |
806 | 17 | generate_dwarf: self.generate_dwarf, |
807 | 17 | modules_by_filename: self.modules_by_filename.clone(), |
808 | 17 | precompile_load: self.precompile_load.clone(), |
809 | 17 | precompile_save: self.precompile_save.clone(), |
810 | 17 | configure_store: self.configure_store.clone(), |
811 | 17 | ignore_error_messages: self.ignore_error_messages, |
812 | | }; |
813 | 17 | let child = scope.spawn(move || child_cx.run_directives(commands, filename)); |
814 | 17 | threads.insert(name.to_string(), child); |
815 | | } |
816 | 17 | Wait { thread, .. } => { |
817 | 17 | threads |
818 | 17 | .remove(&thread[..]) |
819 | 17 | .ok_or_else(|| format_err!("no thread named `{thread}`"))? |
820 | 17 | .join() |
821 | 17 | .unwrap()?; |
822 | | } |
823 | | |
824 | | AssertSuspension { .. } => { |
825 | 0 | bail!("unimplemented wast directive"); |
826 | | } |
827 | | |
828 | | AssertMalformedCustom { |
829 | | file: _, |
830 | | text: _, |
831 | | line: _, |
832 | | } |
833 | | | AssertInvalidCustom { |
834 | | file: _, |
835 | | text: _, |
836 | | line: _, |
837 | 0 | } => bail!("unimplemented wast directives"), |
838 | | } |
839 | | |
840 | 429k | Ok(()) |
841 | 429k | } |
842 | | |
843 | | /// Run a wast script from a file. |
844 | 0 | pub fn run_file(&mut self, path: &Path) -> Result<()> { |
845 | 0 | match &self.precompile_load { |
846 | 0 | Some(precompile) => { |
847 | 0 | let file = precompile |
848 | 0 | .join(path.file_name().unwrap()) |
849 | 0 | .with_extension("json"); |
850 | 0 | let json = std::fs::read_to_string(&file) |
851 | 0 | .with_context(|| format!("failed to read {file:?}"))?; |
852 | 0 | let wast = serde_json::from_str::<json_from_wast::Wast<'_>>(&json)?; |
853 | 0 | self.run_directives(wast.commands, &wast.source_filename) |
854 | | } |
855 | | None => { |
856 | 0 | let bytes = std::fs::read(path) |
857 | 0 | .with_context(|| format!("failed to read `{}`", path.display()))?; |
858 | 0 | self.run_wast(path.to_str().unwrap(), &bytes) |
859 | | } |
860 | | } |
861 | 0 | } |
862 | | |
863 | | /// Whether or not to generate DWARF debugging information in custom |
864 | | /// sections in modules being tested. |
865 | 0 | pub fn generate_dwarf(&mut self, enable: bool) -> &mut Self { |
866 | 0 | self.generate_dwarf = enable; |
867 | 0 | self |
868 | 0 | } |
869 | | |
870 | 18.3k | fn match_error_message(&self, expected: &str, err: wasmtime::Error) -> Result<()> { |
871 | 18.3k | if self.ignore_error_messages { |
872 | 17.8k | return Ok(()); |
873 | 512 | } |
874 | 512 | let actual = format!("{err:?}"); |
875 | 512 | if actual.contains(expected) { |
876 | 512 | return Ok(()); |
877 | 0 | } |
878 | 0 | bail!("assert_invalid: expected \"{expected}\", got \"{actual}\"",) |
879 | 18.3k | } |
880 | | } |