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
16pub struct WastContext<T: 'static> {
19 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#[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 pub fn new(store: Store<T>, async_: Async) -> Self {
98 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 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 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 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 _ => {}
320 }
321 }
322 }
323 self.current = Some(InstanceKind::Component(instance));
324 }
325 }
326 Ok(())
327 }
328
329 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 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 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 || (expected.contains("uninitialized element 2") && actual.contains("uninitialized element"))
451 || (expected.contains("null function") && (actual.contains("uninitialized element") || actual.contains("null reference")))
453 || (expected.contains("null") && expected.contains("reference") && actual.contains("null reference"))
455 {
456 return Ok(());
457 }
458 bail!("expected '{}', got '{}'", expected, actual)
459 }
460
461 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 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 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 if test.contains("spec_testsuite") {
709 return true;
710 }
711
712 false
715}