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