wasmtime_wast/
wast.rs

1#[cfg(feature = "component-model")]
2use crate::component;
3use crate::core;
4use crate::spectest::*;
5use anyhow::{Context as _, anyhow, bail};
6use std::collections::HashMap;
7use std::path::Path;
8use std::str;
9use std::thread;
10use wasmtime::*;
11use wast::core::{EncodeOptions, GenerateDwarf};
12use wast::lexer::Lexer;
13use wast::parser::{self, ParseBuffer};
14use wast::{QuoteWat, Wast, WastArg, WastDirective, WastExecute, WastInvoke, WastRet, Wat};
15
16/// The wast test script language allows modules to be defined and actions
17/// to be performed on them.
18pub struct WastContext<T: 'static> {
19    /// Wast files have a concept of a "current" module, which is the most
20    /// recently defined.
21    current: Option<InstanceKind>,
22    core_linker: Linker<T>,
23    modules: HashMap<String, ModuleKind>,
24    #[cfg(feature = "component-model")]
25    component_linker: component::Linker<T>,
26    pub(crate) store: Store<T>,
27    pub(crate) async_runtime: Option<tokio::runtime::Runtime>,
28    generate_dwarf: bool,
29}
30
31enum Outcome<T = Results> {
32    Ok(T),
33    Trap(Error),
34}
35
36impl<T> Outcome<T> {
37    fn map<U>(self, map: impl FnOnce(T) -> U) -> Outcome<U> {
38        match self {
39            Outcome::Ok(t) => Outcome::Ok(map(t)),
40            Outcome::Trap(t) => Outcome::Trap(t),
41        }
42    }
43
44    fn into_result(self) -> Result<T> {
45        match self {
46            Outcome::Ok(t) => Ok(t),
47            Outcome::Trap(t) => Err(t),
48        }
49    }
50}
51
52#[derive(Debug)]
53enum Results {
54    Core(Vec<Val>),
55    #[cfg(feature = "component-model")]
56    Component(Vec<component::Val>),
57}
58
59#[derive(Clone)]
60enum ModuleKind {
61    Core(Module),
62    #[cfg(feature = "component-model")]
63    Component(component::Component),
64}
65
66enum InstanceKind {
67    Core(Instance),
68    #[cfg(feature = "component-model")]
69    Component(component::Instance),
70}
71
72enum Export {
73    Core(Extern),
74    #[cfg(feature = "component-model")]
75    Component(component::Func),
76}
77
78/// Whether or not to use async APIs when calling wasm during wast testing.
79///
80/// Passed to [`WastContext::new`].
81#[derive(Copy, Clone, PartialEq)]
82#[expect(missing_docs, reason = "self-describing variants")]
83pub enum Async {
84    Yes,
85    No,
86}
87
88impl<T> WastContext<T>
89where
90    T: Clone + Send + 'static,
91{
92    /// Construct a new instance of `WastContext`.
93    ///
94    /// Note that the provided `Store<T>` must have `Config::async_support`
95    /// enabled as all functions will be run with `call_async`. This is done to
96    /// support the component model async features that tests might use.
97    pub fn new(store: Store<T>, async_: Async) -> Self {
98        // Spec tests will redefine the same module/name sometimes, so we need
99        // to allow shadowing in the linker which picks the most recent
100        // definition as what to link when linking.
101        let mut core_linker = Linker::new(store.engine());
102        core_linker.allow_shadowing(true);
103        Self {
104            current: None,
105            core_linker,
106            #[cfg(feature = "component-model")]
107            component_linker: {
108                let mut linker = component::Linker::new(store.engine());
109                linker.allow_shadowing(true);
110                linker
111            },
112            store,
113            modules: Default::default(),
114            async_runtime: if async_ == Async::Yes {
115                Some(
116                    tokio::runtime::Builder::new_current_thread()
117                        .build()
118                        .unwrap(),
119                )
120            } else {
121                None
122            },
123            generate_dwarf: true,
124        }
125    }
126
127    fn get_export(&mut self, module: Option<&str>, name: &str) -> Result<Export> {
128        if let Some(module) = module {
129            return Ok(Export::Core(
130                self.core_linker
131                    .get(&mut self.store, module, name)
132                    .ok_or_else(|| anyhow!("no item named `{}::{}` found", module, name))?,
133            ));
134        }
135
136        let cur = self
137            .current
138            .as_ref()
139            .ok_or_else(|| anyhow!("no previous instance found"))?;
140        Ok(match cur {
141            InstanceKind::Core(i) => Export::Core(
142                i.get_export(&mut self.store, name)
143                    .ok_or_else(|| anyhow!("no item named `{}` found", name))?,
144            ),
145            #[cfg(feature = "component-model")]
146            InstanceKind::Component(i) => Export::Component(
147                i.get_func(&mut self.store, name)
148                    .ok_or_else(|| anyhow!("no func named `{}` found", name))?,
149            ),
150        })
151    }
152
153    fn instantiate_module(&mut self, module: &Module) -> Result<Outcome<Instance>> {
154        let instance = match &self.async_runtime {
155            Some(rt) => rt.block_on(self.core_linker.instantiate_async(&mut self.store, &module)),
156            None => self.core_linker.instantiate(&mut self.store, &module),
157        };
158        Ok(match instance {
159            Ok(i) => Outcome::Ok(i),
160            Err(e) => Outcome::Trap(e),
161        })
162    }
163
164    #[cfg(feature = "component-model")]
165    fn instantiate_component(
166        &mut self,
167        component: &component::Component,
168    ) -> Result<Outcome<(component::Component, component::Instance)>> {
169        let instance = match &self.async_runtime {
170            Some(rt) => rt.block_on(
171                self.component_linker
172                    .instantiate_async(&mut self.store, &component),
173            ),
174            None => self
175                .component_linker
176                .instantiate(&mut self.store, &component),
177        };
178        Ok(match instance {
179            Ok(i) => Outcome::Ok((component.clone(), i)),
180            Err(e) => Outcome::Trap(e),
181        })
182    }
183
184    /// Register "spectest" which is used by the spec testsuite.
185    pub fn register_spectest(&mut self, config: &SpectestConfig) -> Result<()> {
186        link_spectest(&mut self.core_linker, &mut self.store, config)?;
187        #[cfg(feature = "component-model")]
188        link_component_spectest(&mut self.component_linker)?;
189        Ok(())
190    }
191
192    /// Perform the action portion of a command.
193    fn perform_execute(
194        &mut self,
195        exec: WastExecute<'_>,
196        filename: &str,
197        wast: &str,
198    ) -> Result<Outcome> {
199        match exec {
200            WastExecute::Invoke(invoke) => self.perform_invoke(invoke),
201            WastExecute::Wat(module) => Ok(
202                match self.module_definition(QuoteWat::Wat(module), filename, wast)? {
203                    (_, ModuleKind::Core(module)) => self
204                        .instantiate_module(&module)?
205                        .map(|_| Results::Core(Vec::new())),
206                    #[cfg(feature = "component-model")]
207                    (_, ModuleKind::Component(component)) => self
208                        .instantiate_component(&component)?
209                        .map(|_| Results::Component(Vec::new())),
210                },
211            ),
212            WastExecute::Get { module, global, .. } => self.get(module.map(|s| s.name()), global),
213        }
214    }
215
216    fn perform_invoke(&mut self, exec: WastInvoke<'_>) -> Result<Outcome> {
217        match self.get_export(exec.module.map(|i| i.name()), exec.name)? {
218            Export::Core(export) => {
219                let func = export
220                    .into_func()
221                    .ok_or_else(|| anyhow!("no function named `{}`", exec.name))?;
222                let values = exec
223                    .args
224                    .iter()
225                    .map(|v| match v {
226                        WastArg::Core(v) => core::val(self, v),
227                        _ => bail!("expected core function, found other other argument {v:?}"),
228                    })
229                    .collect::<Result<Vec<_>>>()?;
230
231                let mut results = vec![Val::null_func_ref(); func.ty(&self.store).results().len()];
232                let result = match &self.async_runtime {
233                    Some(rt) => {
234                        rt.block_on(func.call_async(&mut self.store, &values, &mut results))
235                    }
236                    None => func.call(&mut self.store, &values, &mut results),
237                };
238
239                Ok(match result {
240                    Ok(()) => Outcome::Ok(Results::Core(results)),
241                    Err(e) => Outcome::Trap(e),
242                })
243            }
244            #[cfg(feature = "component-model")]
245            Export::Component(func) => {
246                let values = exec
247                    .args
248                    .iter()
249                    .map(|v| match v {
250                        WastArg::Component(v) => component::val(v),
251                        _ => bail!("expected component function, found other argument {v:?}"),
252                    })
253                    .collect::<Result<Vec<_>>>()?;
254
255                let mut results =
256                    vec![component::Val::Bool(false); func.results(&self.store).len()];
257                let result = match &self.async_runtime {
258                    Some(rt) => {
259                        rt.block_on(func.call_async(&mut self.store, &values, &mut results))
260                    }
261                    None => func.call(&mut self.store, &values, &mut results),
262                };
263                Ok(match result {
264                    Ok(()) => {
265                        match &self.async_runtime {
266                            Some(rt) => rt.block_on(func.post_return_async(&mut self.store))?,
267                            None => func.post_return(&mut self.store)?,
268                        }
269
270                        Outcome::Ok(Results::Component(results))
271                    }
272                    Err(e) => Outcome::Trap(e),
273                })
274            }
275        }
276    }
277
278    /// Instantiates the `module` provided and registers the instance under the
279    /// `name` provided if successful.
280    fn module(&mut self, name: Option<&str>, module: &ModuleKind) -> Result<()> {
281        match module {
282            ModuleKind::Core(module) => {
283                let instance = match self.instantiate_module(&module)? {
284                    Outcome::Ok(i) => i,
285                    Outcome::Trap(e) => return Err(e).context("instantiation failed"),
286                };
287                if let Some(name) = name {
288                    self.core_linker.instance(&mut self.store, name, instance)?;
289                }
290                self.current = Some(InstanceKind::Core(instance));
291            }
292            #[cfg(feature = "component-model")]
293            ModuleKind::Component(module) => {
294                let (component, instance) = match self.instantiate_component(&module)? {
295                    Outcome::Ok(i) => i,
296                    Outcome::Trap(e) => return Err(e).context("instantiation failed"),
297                };
298                if let Some(name) = name {
299                    let ty = component.component_type();
300                    let mut linker = self.component_linker.instance(name)?;
301                    let engine = self.store.engine().clone();
302                    for (name, item) in ty.exports(&engine) {
303                        match item {
304                            component::types::ComponentItem::Module(_) => {
305                                let module = instance.get_module(&mut self.store, name).unwrap();
306                                linker.module(name, &module)?;
307                            }
308                            component::types::ComponentItem::Resource(_) => {
309                                let resource =
310                                    instance.get_resource(&mut self.store, name).unwrap();
311                                linker.resource(name, resource, |_, _| Ok(()))?;
312                            }
313                            // TODO: should ideally reflect more than just
314                            // modules/resources into the linker's namespace
315                            // but that's not easily supported today for host
316                            // functions due to the inability to take a
317                            // function from one instance and put it into the
318                            // linker (must go through the host right now).
319                            _ => {}
320                        }
321                    }
322                }
323                self.current = Some(InstanceKind::Component(instance));
324            }
325        }
326        Ok(())
327    }
328
329    /// Compiles the module `wat` into binary and returns the name found within
330    /// it, if any.
331    ///
332    /// This will not register the name within `self.modules`.
333    fn module_definition<'a>(
334        &mut self,
335        mut wat: QuoteWat<'a>,
336        filename: &str,
337        wast: &str,
338    ) -> Result<(Option<&'a str>, ModuleKind)> {
339        let (is_module, name) = match &wat {
340            QuoteWat::Wat(Wat::Module(m)) => (true, m.id),
341            QuoteWat::QuoteModule(..) => (true, None),
342            QuoteWat::Wat(Wat::Component(m)) => (false, m.id),
343            QuoteWat::QuoteComponent(..) => (false, None),
344        };
345        let bytes = match &mut wat {
346            QuoteWat::Wat(wat) => {
347                let mut opts = EncodeOptions::new();
348                if self.generate_dwarf {
349                    opts.dwarf(filename.as_ref(), wast, GenerateDwarf::Lines);
350                }
351                opts.encode_wat(wat)?
352            }
353            _ => wat.encode()?,
354        };
355        if is_module {
356            let module = Module::new(self.store.engine(), &bytes)?;
357            Ok((name.map(|n| n.name()), ModuleKind::Core(module)))
358        } else {
359            #[cfg(feature = "component-model")]
360            {
361                let component = component::Component::new(self.store.engine(), &bytes)?;
362                Ok((name.map(|n| n.name()), ModuleKind::Component(component)))
363            }
364            #[cfg(not(feature = "component-model"))]
365            bail!("component-model support not enabled");
366        }
367    }
368
369    /// Register an instance to make it available for performing actions.
370    fn register(&mut self, name: Option<&str>, as_name: &str) -> Result<()> {
371        match name {
372            Some(name) => self.core_linker.alias_module(name, as_name),
373            None => {
374                let current = self
375                    .current
376                    .as_ref()
377                    .ok_or(anyhow!("no previous instance"))?;
378                match current {
379                    InstanceKind::Core(current) => {
380                        self.core_linker
381                            .instance(&mut self.store, as_name, *current)?;
382                    }
383                    #[cfg(feature = "component-model")]
384                    InstanceKind::Component(_) => {
385                        bail!("register not implemented for components");
386                    }
387                }
388                Ok(())
389            }
390        }
391    }
392
393    /// Get the value of an exported global from an instance.
394    fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result<Outcome> {
395        let global = match self.get_export(instance_name, field)? {
396            Export::Core(e) => e
397                .into_global()
398                .ok_or_else(|| anyhow!("no global named `{field}`"))?,
399            #[cfg(feature = "component-model")]
400            Export::Component(_) => bail!("no global named `{field}`"),
401        };
402        Ok(Outcome::Ok(Results::Core(vec![
403            global.get(&mut self.store),
404        ])))
405    }
406
407    fn assert_return(&mut self, result: Outcome, results: &[WastRet<'_>]) -> Result<()> {
408        match result.into_result()? {
409            Results::Core(values) => {
410                if values.len() != results.len() {
411                    bail!("expected {} results found {}", results.len(), values.len());
412                }
413                for (i, (v, e)) in values.iter().zip(results).enumerate() {
414                    let e = match e {
415                        WastRet::Core(core) => core,
416                        _ => bail!("expected core value found other value {e:?}"),
417                    };
418                    core::match_val(&mut self.store, v, e)
419                        .with_context(|| format!("result {i} didn't match"))?;
420                }
421            }
422            #[cfg(feature = "component-model")]
423            Results::Component(values) => {
424                if values.len() != results.len() {
425                    bail!("expected {} results found {}", results.len(), values.len());
426                }
427                for (i, (v, e)) in values.iter().zip(results).enumerate() {
428                    let e = match e {
429                        WastRet::Component(val) => val,
430                        _ => bail!("expected component value found other value {e:?}"),
431                    };
432                    component::match_val(e, v)
433                        .with_context(|| format!("result {i} didn't match"))?;
434                }
435            }
436        }
437        Ok(())
438    }
439
440    fn assert_trap(&self, result: Outcome, expected: &str) -> Result<()> {
441        let trap = match result {
442            Outcome::Ok(values) => bail!("expected trap, got {:?}", values),
443            Outcome::Trap(t) => t,
444        };
445        let actual = format!("{trap:?}");
446        if actual.contains(expected)
447            // `bulk-memory-operations/bulk.wast` checks for a message that
448            // specifies which element is uninitialized, but our traps don't
449            // shepherd that information out.
450            || (expected.contains("uninitialized element 2") && actual.contains("uninitialized element"))
451            // function references call_ref
452            || (expected.contains("null function") && (actual.contains("uninitialized element") || actual.contains("null reference")))
453            // GC tests say "null $kind reference" but we just say "null reference".
454            || (expected.contains("null") && expected.contains("reference") && actual.contains("null reference"))
455        {
456            return Ok(());
457        }
458        bail!("expected '{}', got '{}'", expected, actual)
459    }
460
461    /// Run a wast script from a byte buffer.
462    pub fn run_buffer(&mut self, filename: &str, wast: &[u8]) -> Result<()> {
463        let wast = str::from_utf8(wast)?;
464
465        let adjust_wast = |mut err: wast::Error| {
466            err.set_path(filename.as_ref());
467            err.set_text(wast);
468            err
469        };
470
471        let mut lexer = Lexer::new(wast);
472        lexer.allow_confusing_unicode(filename.ends_with("names.wast"));
473        let mut buf = ParseBuffer::new_with_lexer(lexer).map_err(adjust_wast)?;
474        buf.track_instr_spans(self.generate_dwarf);
475        let ast = parser::parse::<Wast>(&buf).map_err(adjust_wast)?;
476
477        self.run_directives(ast.directives, filename, wast)
478    }
479
480    fn run_directives(
481        &mut self,
482        directives: Vec<WastDirective<'_>>,
483        filename: &str,
484        wast: &str,
485    ) -> Result<()> {
486        let adjust_wast = |mut err: wast::Error| {
487            err.set_path(filename.as_ref());
488            err.set_text(wast);
489            err
490        };
491
492        thread::scope(|scope| {
493            let mut threads = HashMap::new();
494            for directive in directives {
495                let sp = directive.span();
496                if log::log_enabled!(log::Level::Debug) {
497                    let (line, col) = sp.linecol_in(wast);
498                    log::debug!("running directive on {}:{}:{}", filename, line + 1, col);
499                }
500                self.run_directive(directive, filename, wast, &scope, &mut threads)
501                    .map_err(|e| match e.downcast() {
502                        Ok(err) => adjust_wast(err).into(),
503                        Err(e) => e,
504                    })
505                    .with_context(|| {
506                        let (line, col) = sp.linecol_in(wast);
507                        format!("failed directive on {}:{}:{}", filename, line + 1, col)
508                    })?;
509            }
510            Ok(())
511        })
512    }
513
514    fn run_directive<'a>(
515        &mut self,
516        directive: WastDirective<'a>,
517        filename: &'a str,
518        wast: &'a str,
519        scope: &'a thread::Scope<'a, '_>,
520        threads: &mut HashMap<&'a str, thread::ScopedJoinHandle<'a, Result<()>>>,
521    ) -> Result<()>
522    where
523        T: 'a,
524    {
525        use wast::WastDirective::*;
526
527        match directive {
528            Module(module) => {
529                let (name, module) = self.module_definition(module, filename, wast)?;
530                self.module(name, &module)?;
531            }
532            ModuleDefinition(module) => {
533                let (name, module) = self.module_definition(module, filename, wast)?;
534                if let Some(name) = name {
535                    self.modules.insert(name.to_string(), module.clone());
536                }
537            }
538            ModuleInstance {
539                instance,
540                module,
541                span: _,
542            } => {
543                let module = module
544                    .and_then(|n| self.modules.get(n.name()))
545                    .cloned()
546                    .ok_or_else(|| anyhow!("no module named {module:?}"))?;
547                self.module(instance.map(|n| n.name()), &module)?;
548            }
549            Register {
550                span: _,
551                name,
552                module,
553            } => {
554                self.register(module.map(|s| s.name()), name)?;
555            }
556            Invoke(i) => {
557                self.perform_invoke(i)?;
558            }
559            AssertReturn {
560                span: _,
561                exec,
562                results,
563            } => {
564                let result = self.perform_execute(exec, filename, wast)?;
565                self.assert_return(result, &results)?;
566            }
567            AssertTrap {
568                span: _,
569                exec,
570                message,
571            } => {
572                let result = self.perform_execute(exec, filename, wast)?;
573                self.assert_trap(result, message)?;
574            }
575            AssertExhaustion {
576                span: _,
577                call,
578                message,
579            } => {
580                let result = self.perform_invoke(call)?;
581                self.assert_trap(result, message)?;
582            }
583            AssertInvalid {
584                span: _,
585                module,
586                message,
587            } => {
588                let err = match self.module_definition(module, filename, wast) {
589                    Ok(_) => bail!("expected module to fail to build"),
590                    Err(e) => e,
591                };
592                let error_message = format!("{err:?}");
593                if !is_matching_assert_invalid_error_message(filename, &message, &error_message) {
594                    bail!(
595                        "assert_invalid: expected \"{}\", got \"{}\"",
596                        message,
597                        error_message
598                    )
599                }
600            }
601            AssertMalformed {
602                module,
603                span: _,
604                message: _,
605            } => {
606                if let Ok(_) = self.module_definition(module, filename, wast) {
607                    bail!("expected malformed module to fail to instantiate");
608                }
609            }
610            AssertUnlinkable {
611                span: _,
612                module,
613                message,
614            } => {
615                let (name, module) =
616                    self.module_definition(QuoteWat::Wat(module), filename, wast)?;
617                let err = match self.module(name, &module) {
618                    Ok(_) => bail!("expected module to fail to link"),
619                    Err(e) => e,
620                };
621                let error_message = format!("{err:?}");
622                if !error_message.contains(&message) {
623                    bail!(
624                        "assert_unlinkable: expected {}, got {}",
625                        message,
626                        error_message
627                    )
628                }
629            }
630            AssertException { .. } => bail!("unimplemented assert_exception"),
631
632            Thread(thread) => {
633                let mut core_linker = Linker::new(self.store.engine());
634                if let Some(id) = thread.shared_module {
635                    let items = self
636                        .core_linker
637                        .iter(&mut self.store)
638                        .filter(|(module, _, _)| *module == id.name())
639                        .collect::<Vec<_>>();
640                    for (module, name, item) in items {
641                        core_linker.define(&mut self.store, module, name, item)?;
642                    }
643                }
644                let mut child_cx = WastContext {
645                    current: None,
646                    core_linker,
647                    #[cfg(feature = "component-model")]
648                    component_linker: component::Linker::new(self.store.engine()),
649                    store: Store::new(self.store.engine(), self.store.data().clone()),
650                    modules: self.modules.clone(),
651                    async_runtime: self.async_runtime.as_ref().map(|_| {
652                        tokio::runtime::Builder::new_current_thread()
653                            .build()
654                            .unwrap()
655                    }),
656                    generate_dwarf: self.generate_dwarf,
657                };
658                let name = thread.name.name();
659                let child =
660                    scope.spawn(move || child_cx.run_directives(thread.directives, filename, wast));
661                threads.insert(name, child);
662            }
663
664            Wait { thread, .. } => {
665                let name = thread.name();
666                threads
667                    .remove(name)
668                    .ok_or_else(|| anyhow!("no thread named `{name}`"))?
669                    .join()
670                    .unwrap()?;
671            }
672
673            AssertSuspension { .. } => {
674                bail!("unimplemented wast directive");
675            }
676        }
677
678        Ok(())
679    }
680
681    /// Run a wast script from a file.
682    pub fn run_file(&mut self, path: &Path) -> Result<()> {
683        let bytes =
684            std::fs::read(path).with_context(|| format!("failed to read `{}`", path.display()))?;
685        self.run_buffer(path.to_str().unwrap(), &bytes)
686    }
687
688    /// Whether or not to generate DWARF debugging information in custom
689    /// sections in modules being tested.
690    pub fn generate_dwarf(&mut self, enable: bool) -> &mut Self {
691        self.generate_dwarf = enable;
692        self
693    }
694}
695
696fn is_matching_assert_invalid_error_message(test: &str, expected: &str, actual: &str) -> bool {
697    if actual.contains(expected) {
698        return true;
699    }
700
701    // Historically wasmtime/wasm-tools tried to match the upstream error
702    // message. This generally led to a large sequence of matches here which is
703    // not easy to maintain and is particularly difficult when test suites and
704    // proposals conflict with each other (e.g. one asserts one error message
705    // and another asserts a different error message). Overall we didn't benefit
706    // a whole lot from trying to match errors so just assume the error is
707    // roughly the same and otherwise don't try to match it.
708    if test.contains("spec_testsuite") {
709        return true;
710    }
711
712    // we are in control over all non-spec tests so all the error messages
713    // there should exactly match the `assert_invalid` or such
714    false
715}