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