wasmtime_wast/
wast.rs

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