Skip to main content

wasmtime_wast/
wast.rs

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