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
15pub struct WastContext<T> {
18 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#[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 pub fn new(store: Store<T>, async_: Async) -> Self {
96 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 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 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 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 _ => {}
312 }
313 }
314 }
315 self.current = Some(InstanceKind::Component(instance));
316 }
317 }
318 Ok(())
319 }
320
321 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 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 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 || (expected.contains("uninitialized element 2") && actual.contains("uninitialized element"))
432 || (expected.contains("null function") && (actual.contains("uninitialized element") || actual.contains("null reference")))
434 || (expected.contains("null") && expected.contains("reference") && actual.contains("null reference"))
436 {
437 return Ok(());
438 }
439 bail!("expected '{}', got '{}'", expected, actual)
440 }
441
442 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 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 if test.contains("spec_testsuite") {
680 return true;
681 }
682
683 false
686}