1use anyhow::{Context, Result};
2use serde::de::DeserializeOwned;
3use serde_derive::Deserialize;
4use std::fmt;
5use std::fs;
6use std::path::Path;
7use std::path::PathBuf;
8
9pub mod limits {
19 pub const MEMORY_SIZE: usize = 805 << 16;
20 pub const MEMORIES: u32 = 450;
21 pub const TABLES: u32 = 200;
22 pub const MEMORIES_PER_MODULE: u32 = 9;
23 pub const TABLES_PER_MODULE: u32 = 5;
24 pub const COMPONENT_INSTANCES: u32 = 50;
25 pub const CORE_INSTANCES: u32 = 900;
26 pub const TABLE_ELEMENTS: usize = 1000;
27 pub const CORE_INSTANCE_SIZE: usize = 64 * 1024;
28}
29
30pub fn find_tests(root: &Path) -> Result<Vec<WastTest>> {
33 let mut tests = Vec::new();
34 add_tests(&mut tests, &root.join("tests/spec_testsuite"), false)?;
35 add_tests(&mut tests, &root.join("tests/misc_testsuite"), true)?;
36 Ok(tests)
37}
38
39fn add_tests(tests: &mut Vec<WastTest>, path: &Path, has_config: bool) -> Result<()> {
40 for entry in path.read_dir().context("failed to read directory")? {
41 let entry = entry.context("failed to read directory entry")?;
42 let path = entry.path();
43 if entry
44 .file_type()
45 .context("failed to get file type")?
46 .is_dir()
47 {
48 add_tests(tests, &path, has_config).context("failed to read sub-directory")?;
49 continue;
50 }
51
52 if path.extension().and_then(|s| s.to_str()) != Some("wast") {
53 continue;
54 }
55
56 let contents =
57 fs::read_to_string(&path).with_context(|| format!("failed to read test: {path:?}"))?;
58 let config = if has_config {
59 parse_test_config(&contents, ";;!")
60 .with_context(|| format!("failed to parse test configuration: {path:?}"))?
61 } else {
62 spec_test_config(&path)
63 };
64 tests.push(WastTest {
65 path,
66 contents,
67 config,
68 })
69 }
70 Ok(())
71}
72
73fn spec_test_config(test: &Path) -> TestConfig {
74 let mut ret = TestConfig::default();
75 ret.spec_test = Some(true);
76 match spec_proposal_from_path(test) {
77 Some("multi-memory") => {
78 ret.multi_memory = Some(true);
79 ret.reference_types = Some(true);
80 ret.simd = Some(true);
81 }
82 Some("wide-arithmetic") => {
83 ret.wide_arithmetic = Some(true);
84 }
85 Some("threads") => {
86 ret.threads = Some(true);
87 ret.reference_types = Some(false);
88 }
89 Some("tail-call") => {
90 ret.tail_call = Some(true);
91 ret.reference_types = Some(true);
92 }
93 Some("relaxed-simd") => {
94 ret.relaxed_simd = Some(true);
95 }
96 Some("memory64") => {
97 ret.memory64 = Some(true);
98 ret.tail_call = Some(true);
99 ret.gc = Some(true);
100 ret.extended_const = Some(true);
101 ret.multi_memory = Some(true);
102 ret.relaxed_simd = Some(true);
103 }
104 Some("extended-const") => {
105 ret.extended_const = Some(true);
106 ret.reference_types = Some(true);
107 }
108 Some("custom-page-sizes") => {
109 ret.custom_page_sizes = Some(true);
110 ret.multi_memory = Some(true);
111 ret.memory64 = Some(true);
112
113 if test.ends_with("memory_max.wast") || test.ends_with("memory_max_i64.wast") {
116 ret.hogs_memory = Some(true);
117 }
118 }
119 Some("exception-handling") => {
120 ret.reference_types = Some(true);
121 ret.exceptions = Some(true);
122 if test.parent().unwrap().ends_with("legacy") {
123 ret.legacy_exceptions = Some(true);
124 }
125 }
126 Some("gc") => {
127 ret.gc = Some(true);
128 ret.tail_call = Some(true);
129 }
130 Some("function-references") => {
131 ret.function_references = Some(true);
132 ret.tail_call = Some(true);
133 }
134 Some("annotations") => {
135 ret.simd = Some(true);
136 }
137 Some("wasm-3.0") => {
138 ret.simd = Some(true);
139 ret.relaxed_simd = Some(true);
140 ret.multi_memory = Some(true);
141 ret.gc = Some(true);
142 ret.reference_types = Some(true);
143 ret.memory64 = Some(true);
144 ret.tail_call = Some(true);
145 ret.extended_const = Some(true);
146
147 if test.ends_with("tag.wast")
153 || test.ends_with("instance.wast")
154 || test.ends_with("throw.wast")
155 || test.ends_with("throw_ref.wast")
156 || test.ends_with("try_table.wast")
157 || test.ends_with("ref_null.wast")
158 || test.ends_with("imports.wast")
159 {
160 ret.exceptions = Some(true);
161 }
162 if test.parent().unwrap().ends_with("legacy") {
163 ret.legacy_exceptions = Some(true);
164 }
165
166 if test.ends_with("memory.wast")
175 || test.ends_with("table.wast")
176 || test.ends_with("memory64.wast")
177 {
178 ret.hogs_memory = Some(true);
179 }
180 }
181 Some(proposal) => panic!("unsuported proposal {proposal:?}"),
182 None => {
183 ret.reference_types = Some(true);
184 ret.simd = Some(true);
185 }
186 }
187
188 ret
189}
190
191pub fn parse_test_config<T>(wat: &str, comment: &'static str) -> Result<T>
194where
195 T: DeserializeOwned,
196{
197 let config_lines: Vec<_> = wat
200 .lines()
201 .take_while(|l| l.starts_with(comment))
202 .map(|l| &l[comment.len()..])
203 .collect();
204 let config_text = config_lines.join("\n");
205
206 toml::from_str(&config_text).context("failed to parse the test configuration")
207}
208
209#[derive(Clone)]
211pub struct WastTest {
212 pub path: PathBuf,
213 pub contents: String,
214 pub config: TestConfig,
215}
216
217impl fmt::Debug for WastTest {
218 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219 f.debug_struct("WastTest")
220 .field("path", &self.path)
221 .field("contents", &"...")
222 .field("config", &self.config)
223 .finish()
224 }
225}
226
227macro_rules! foreach_config_option {
228 ($m:ident) => {
229 $m! {
230 memory64
231 custom_page_sizes
232 multi_memory
233 threads
234 shared_everything_threads
235 gc
236 function_references
237 relaxed_simd
238 reference_types
239 tail_call
240 extended_const
241 wide_arithmetic
242 hogs_memory
243 nan_canonicalization
244 component_model_async
245 component_model_async_builtins
246 component_model_async_stackful
247 component_model_error_context
248 component_model_gc
249 simd
250 gc_types
251 exceptions
252 legacy_exceptions
253 stack_switching
254 spec_test
255 }
256 };
257}
258
259macro_rules! define_test_config {
260 ($($option:ident)*) => {
261 #[derive(Debug, PartialEq, Default, Deserialize, Clone)]
264 #[serde(deny_unknown_fields)]
265 pub struct TestConfig {
266 $(pub $option: Option<bool>,)*
267 }
268
269 impl TestConfig {
270 $(
271 pub fn $option(&self) -> bool {
272 self.$option.unwrap_or(false)
273 }
274 )*
275 }
276 }
277}
278
279foreach_config_option!(define_test_config);
280
281impl TestConfig {
282 pub fn options_mut(&mut self) -> impl Iterator<Item = (&'static str, &mut Option<bool>)> {
284 macro_rules! mk {
285 ($($option:ident)*) => {
286 [
287 $((stringify!($option), &mut self.$option),)*
288 ].into_iter()
289 }
290 }
291 foreach_config_option!(mk)
292 }
293}
294
295#[derive(Debug)]
297pub struct WastConfig {
298 pub compiler: Compiler,
300 pub pooling: bool,
302 pub collector: Collector,
304}
305
306#[derive(PartialEq, Debug, Copy, Clone)]
308pub enum Compiler {
309 CraneliftNative,
316
317 Winch,
322
323 CraneliftPulley,
330}
331
332impl Compiler {
333 pub fn should_fail(&self, config: &TestConfig) -> bool {
344 match self {
345 Compiler::CraneliftNative => config.legacy_exceptions(),
346
347 Compiler::Winch => {
348 let unsupported_base = config.gc()
349 || config.tail_call()
350 || config.function_references()
351 || config.gc()
352 || config.relaxed_simd()
353 || config.gc_types()
354 || config.exceptions()
355 || config.legacy_exceptions()
356 || config.stack_switching()
357 || config.legacy_exceptions();
358
359 if cfg!(target_arch = "x86_64") {
360 return unsupported_base;
361 }
362
363 if cfg!(target_arch = "aarch64") {
364 return unsupported_base
365 || config.wide_arithmetic()
366 || (config.simd() && !config.spec_test())
367 || config.threads();
368 }
369
370 true
371 }
372
373 Compiler::CraneliftPulley => {
374 config.threads() || config.legacy_exceptions() || config.stack_switching()
375 }
376 }
377 }
378
379 pub fn supports_host(&self) -> bool {
382 match self {
383 Compiler::CraneliftNative => {
384 cfg!(target_arch = "x86_64")
385 || cfg!(target_arch = "aarch64")
386 || cfg!(target_arch = "riscv64")
387 || cfg!(target_arch = "s390x")
388 }
389 Compiler::Winch => cfg!(target_arch = "x86_64") || cfg!(target_arch = "aarch64"),
390 Compiler::CraneliftPulley => true,
391 }
392 }
393}
394
395#[derive(PartialEq, Debug, Copy, Clone)]
396pub enum Collector {
397 Auto,
398 Null,
399 DeferredReferenceCounting,
400}
401
402impl WastTest {
403 pub fn test_uses_gc_types(&self) -> bool {
406 self.config.gc() || self.config.function_references()
407 }
408
409 pub fn spec_proposal(&self) -> Option<&str> {
411 spec_proposal_from_path(&self.path)
412 }
413
414 pub fn should_fail(&self, config: &WastConfig) -> bool {
417 if !config.compiler.supports_host() {
418 return true;
419 }
420
421 if config.pooling {
423 let unsupported = [
424 "misc_testsuite/memory64/more-than-4gb.wast",
426 "misc_testsuite/memory-combos.wast",
428 "misc_testsuite/threads/LB.wast",
429 "misc_testsuite/threads/LB_atomic.wast",
430 "misc_testsuite/threads/MP.wast",
431 "misc_testsuite/threads/MP_atomic.wast",
432 "misc_testsuite/threads/MP_wait.wast",
433 "misc_testsuite/threads/SB.wast",
434 "misc_testsuite/threads/SB_atomic.wast",
435 "misc_testsuite/threads/atomics_notify.wast",
436 "misc_testsuite/threads/atomics_wait_address.wast",
437 "misc_testsuite/threads/wait_notify.wast",
438 "spec_testsuite/proposals/threads/atomic.wast",
439 "spec_testsuite/proposals/threads/exports.wast",
440 "spec_testsuite/proposals/threads/memory.wast",
441 ];
442
443 if unsupported.iter().any(|part| self.path.ends_with(part)) {
444 return true;
445 }
446 }
447
448 if config.compiler.should_fail(&self.config) {
449 return true;
450 }
451
452 if config.compiler == Compiler::Winch {
454 let unsupported = [
456 "extended-const/elem.wast",
457 "extended-const/global.wast",
458 "misc_testsuite/component-model/modules.wast",
459 "misc_testsuite/externref-id-function.wast",
460 "misc_testsuite/externref-segment.wast",
461 "misc_testsuite/externref-segments.wast",
462 "misc_testsuite/externref-table-dropped-segment-issue-8281.wast",
463 "misc_testsuite/linking-errors.wast",
464 "misc_testsuite/many_table_gets_lead_to_gc.wast",
465 "misc_testsuite/mutable_externref_globals.wast",
466 "misc_testsuite/no-mixup-stack-maps.wast",
467 "misc_testsuite/no-panic.wast",
468 "misc_testsuite/simple_ref_is_null.wast",
469 "misc_testsuite/table_grow_with_funcref.wast",
470 "spec_testsuite/br_table.wast",
471 "spec_testsuite/global.wast",
472 "spec_testsuite/ref_func.wast",
473 "spec_testsuite/ref_is_null.wast",
474 "spec_testsuite/ref_null.wast",
475 "spec_testsuite/select.wast",
476 "spec_testsuite/table_fill.wast",
477 "spec_testsuite/table_get.wast",
478 "spec_testsuite/table_grow.wast",
479 "spec_testsuite/table_set.wast",
480 "spec_testsuite/table_size.wast",
481 "spec_testsuite/elem.wast",
482 "spec_testsuite/linking.wast",
483 ];
484
485 if unsupported.iter().any(|part| self.path.ends_with(part)) {
486 return true;
487 }
488
489 #[cfg(target_arch = "aarch64")]
490 {
491 let unsupported = [
492 "misc_testsuite/int-to-float-splat.wast",
493 "misc_testsuite/issue6562.wast",
494 "misc_testsuite/memory64/simd.wast",
495 "misc_testsuite/simd/almost-extmul.wast",
496 "misc_testsuite/simd/canonicalize-nan.wast",
497 "misc_testsuite/simd/cvt-from-uint.wast",
498 "misc_testsuite/simd/edge-of-memory.wast",
499 "misc_testsuite/simd/interesting-float-splat.wast",
500 "misc_testsuite/simd/issue4807.wast",
501 "misc_testsuite/simd/issue6725-no-egraph-panic.wast",
502 "misc_testsuite/simd/issue_3173_select_v128.wast",
503 "misc_testsuite/simd/issue_3327_bnot_lowering.wast",
504 "misc_testsuite/simd/load_splat_out_of_bounds.wast",
505 "misc_testsuite/simd/replace-lane-preserve.wast",
506 "misc_testsuite/simd/spillslot-size-fuzzbug.wast",
507 "misc_testsuite/simd/sse-cannot-fold-unaligned-loads.wast",
508 "misc_testsuite/simd/unaligned-load.wast",
509 "misc_testsuite/simd/v128-select.wast",
510 "misc_testsuite/winch/issue-10331.wast",
511 "misc_testsuite/winch/issue-10357.wast",
512 "misc_testsuite/winch/issue-10460.wast",
513 "misc_testsuite/winch/replace_lane.wast",
514 "misc_testsuite/winch/simd_multivalue.wast",
515 "misc_testsuite/winch/v128_load_lane_invalid_address.wast",
516 "spec_testsuite/proposals/annotations/simd_lane.wast",
517 "spec_testsuite/proposals/multi-memory/simd_memory-multi.wast",
518 "spec_testsuite/simd_address.wast",
519 "spec_testsuite/simd_align.wast",
520 "spec_testsuite/simd_bit_shift.wast",
521 "spec_testsuite/simd_bitwise.wast",
522 "spec_testsuite/simd_boolean.wast",
523 "spec_testsuite/simd_const.wast",
524 "spec_testsuite/simd_conversions.wast",
525 "spec_testsuite/simd_f32x4.wast",
526 "spec_testsuite/simd_f32x4_arith.wast",
527 "spec_testsuite/simd_f32x4_cmp.wast",
528 "spec_testsuite/simd_f32x4_pmin_pmax.wast",
529 "spec_testsuite/simd_f32x4_rounding.wast",
530 "spec_testsuite/simd_f64x2.wast",
531 "spec_testsuite/simd_f64x2_arith.wast",
532 "spec_testsuite/simd_f64x2_cmp.wast",
533 "spec_testsuite/simd_f64x2_pmin_pmax.wast",
534 "spec_testsuite/simd_f64x2_rounding.wast",
535 "spec_testsuite/simd_i16x8_arith.wast",
536 "spec_testsuite/simd_i16x8_arith2.wast",
537 "spec_testsuite/simd_i16x8_cmp.wast",
538 "spec_testsuite/simd_i16x8_extadd_pairwise_i8x16.wast",
539 "spec_testsuite/simd_i16x8_extmul_i8x16.wast",
540 "spec_testsuite/simd_i16x8_q15mulr_sat_s.wast",
541 "spec_testsuite/simd_i16x8_sat_arith.wast",
542 "spec_testsuite/simd_i32x4_arith.wast",
543 "spec_testsuite/simd_i32x4_arith2.wast",
544 "spec_testsuite/simd_i32x4_cmp.wast",
545 "spec_testsuite/simd_i32x4_dot_i16x8.wast",
546 "spec_testsuite/simd_i32x4_extadd_pairwise_i16x8.wast",
547 "spec_testsuite/simd_i32x4_extmul_i16x8.wast",
548 "spec_testsuite/simd_i32x4_trunc_sat_f32x4.wast",
549 "spec_testsuite/simd_i32x4_trunc_sat_f64x2.wast",
550 "spec_testsuite/simd_i64x2_arith.wast",
551 "spec_testsuite/simd_i64x2_arith2.wast",
552 "spec_testsuite/simd_i64x2_cmp.wast",
553 "spec_testsuite/simd_i64x2_extmul_i32x4.wast",
554 "spec_testsuite/simd_i8x16_arith.wast",
555 "spec_testsuite/simd_i8x16_arith2.wast",
556 "spec_testsuite/simd_i8x16_cmp.wast",
557 "spec_testsuite/simd_i8x16_sat_arith.wast",
558 "spec_testsuite/simd_int_to_int_extend.wast",
559 "spec_testsuite/simd_lane.wast",
560 "spec_testsuite/simd_load.wast",
561 "spec_testsuite/simd_load16_lane.wast",
562 "spec_testsuite/simd_load32_lane.wast",
563 "spec_testsuite/simd_load64_lane.wast",
564 "spec_testsuite/simd_load8_lane.wast",
565 "spec_testsuite/simd_load_extend.wast",
566 "spec_testsuite/simd_load_splat.wast",
567 "spec_testsuite/simd_load_zero.wast",
568 "spec_testsuite/simd_select.wast",
569 "spec_testsuite/simd_splat.wast",
570 "spec_testsuite/simd_store.wast",
571 "spec_testsuite/simd_store16_lane.wast",
572 "spec_testsuite/simd_store32_lane.wast",
573 "spec_testsuite/simd_store64_lane.wast",
574 "spec_testsuite/simd_store8_lane.wast",
575 ];
576
577 if unsupported.iter().any(|part| self.path.ends_with(part)) {
578 return true;
579 }
580 }
581
582 #[cfg(target_arch = "x86_64")]
583 {
584 let unsupported = [
585 "misc_testsuite/simd/canonicalize-nan.wast",
588 ];
589
590 if unsupported.iter().any(|part| self.path.ends_with(part)) {
591 return true;
592 }
593
594 #[cfg(target_arch = "x86_64")]
596 if !(std::is_x86_feature_detected!("avx") && std::is_x86_feature_detected!("avx2"))
597 {
598 let unsupported = [
599 "annotations/simd_lane.wast",
600 "memory64/simd.wast",
601 "misc_testsuite/int-to-float-splat.wast",
602 "misc_testsuite/issue6562.wast",
603 "misc_testsuite/simd/almost-extmul.wast",
604 "misc_testsuite/simd/cvt-from-uint.wast",
605 "misc_testsuite/simd/edge-of-memory.wast",
606 "misc_testsuite/simd/issue_3327_bnot_lowering.wast",
607 "misc_testsuite/simd/issue6725-no-egraph-panic.wast",
608 "misc_testsuite/simd/replace-lane-preserve.wast",
609 "misc_testsuite/simd/spillslot-size-fuzzbug.wast",
610 "misc_testsuite/simd/sse-cannot-fold-unaligned-loads.wast",
611 "misc_testsuite/winch/issue-10331.wast",
612 "misc_testsuite/winch/replace_lane.wast",
613 "spec_testsuite/simd_align.wast",
614 "spec_testsuite/simd_boolean.wast",
615 "spec_testsuite/simd_conversions.wast",
616 "spec_testsuite/simd_f32x4.wast",
617 "spec_testsuite/simd_f32x4_arith.wast",
618 "spec_testsuite/simd_f32x4_cmp.wast",
619 "spec_testsuite/simd_f32x4_pmin_pmax.wast",
620 "spec_testsuite/simd_f32x4_rounding.wast",
621 "spec_testsuite/simd_f64x2.wast",
622 "spec_testsuite/simd_f64x2_arith.wast",
623 "spec_testsuite/simd_f64x2_cmp.wast",
624 "spec_testsuite/simd_f64x2_pmin_pmax.wast",
625 "spec_testsuite/simd_f64x2_rounding.wast",
626 "spec_testsuite/simd_i16x8_cmp.wast",
627 "spec_testsuite/simd_i32x4_cmp.wast",
628 "spec_testsuite/simd_i64x2_arith2.wast",
629 "spec_testsuite/simd_i64x2_cmp.wast",
630 "spec_testsuite/simd_i8x16_arith2.wast",
631 "spec_testsuite/simd_i8x16_cmp.wast",
632 "spec_testsuite/simd_int_to_int_extend.wast",
633 "spec_testsuite/simd_load.wast",
634 "spec_testsuite/simd_load_extend.wast",
635 "spec_testsuite/simd_load_splat.wast",
636 "spec_testsuite/simd_load_zero.wast",
637 "spec_testsuite/simd_splat.wast",
638 "spec_testsuite/simd_store16_lane.wast",
639 "spec_testsuite/simd_store32_lane.wast",
640 "spec_testsuite/simd_store64_lane.wast",
641 "spec_testsuite/simd_store8_lane.wast",
642 "spec_testsuite/simd_load16_lane.wast",
643 "spec_testsuite/simd_load32_lane.wast",
644 "spec_testsuite/simd_load64_lane.wast",
645 "spec_testsuite/simd_load8_lane.wast",
646 "spec_testsuite/simd_bitwise.wast",
647 "misc_testsuite/simd/load_splat_out_of_bounds.wast",
648 "misc_testsuite/simd/unaligned-load.wast",
649 "multi-memory/simd_memory-multi.wast",
650 "misc_testsuite/simd/issue4807.wast",
651 "spec_testsuite/simd_const.wast",
652 "spec_testsuite/simd_i8x16_sat_arith.wast",
653 "spec_testsuite/simd_i64x2_arith.wast",
654 "spec_testsuite/simd_i16x8_arith.wast",
655 "spec_testsuite/simd_i16x8_arith2.wast",
656 "spec_testsuite/simd_i16x8_q15mulr_sat_s.wast",
657 "spec_testsuite/simd_i16x8_sat_arith.wast",
658 "spec_testsuite/simd_i32x4_arith.wast",
659 "spec_testsuite/simd_i32x4_dot_i16x8.wast",
660 "spec_testsuite/simd_i32x4_trunc_sat_f32x4.wast",
661 "spec_testsuite/simd_i32x4_trunc_sat_f64x2.wast",
662 "spec_testsuite/simd_i8x16_arith.wast",
663 "spec_testsuite/simd_bit_shift.wast",
664 "spec_testsuite/simd_lane.wast",
665 "spec_testsuite/simd_i16x8_extmul_i8x16.wast",
666 "spec_testsuite/simd_i32x4_extmul_i16x8.wast",
667 "spec_testsuite/simd_i64x2_extmul_i32x4.wast",
668 "spec_testsuite/simd_i16x8_extadd_pairwise_i8x16.wast",
669 "spec_testsuite/simd_i32x4_extadd_pairwise_i16x8.wast",
670 "spec_testsuite/simd_i32x4_arith2.wast",
671 ];
672
673 if unsupported.iter().any(|part| self.path.ends_with(part)) {
674 return true;
675 }
676 }
677 }
678 }
679
680 if self.config.exceptions() {
683 let unsupported = [
684 "ref_null.wast",
685 "throw.wast",
686 "rethrow.wast",
687 "throw_ref.wast",
688 "try_table.wast",
689 "instance.wast",
690 ];
691 if unsupported.iter().any(|part| self.path.ends_with(part)) {
692 return true;
693 }
694 }
695
696 false
697 }
698}
699
700fn spec_proposal_from_path(path: &Path) -> Option<&str> {
701 let mut iter = path.iter();
702 loop {
703 match iter.next()?.to_str()? {
704 "proposals" => break,
705 _ => {}
706 }
707 }
708 Some(iter.next()?.to_str()?)
709}