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