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 match spec_proposal_from_path(test) {
76 Some("multi-memory") => {
77 ret.multi_memory = Some(true);
78 ret.reference_types = Some(true);
79 ret.simd = Some(true);
80 }
81 Some("wide-arithmetic") => {
82 ret.wide_arithmetic = Some(true);
83 }
84 Some("threads") => {
85 ret.threads = Some(true);
86 ret.reference_types = Some(false);
87 }
88 Some("tail-call") => {
89 ret.tail_call = Some(true);
90 ret.reference_types = Some(true);
91 }
92 Some("relaxed-simd") => {
93 ret.relaxed_simd = Some(true);
94 }
95 Some("memory64") => {
96 ret.memory64 = Some(true);
97 ret.tail_call = Some(true);
98 ret.gc = Some(true);
99 ret.extended_const = Some(true);
100 ret.multi_memory = Some(true);
101 ret.relaxed_simd = Some(true);
102 }
103 Some("extended-const") => {
104 ret.extended_const = Some(true);
105 ret.reference_types = Some(true);
106 }
107 Some("custom-page-sizes") => {
108 ret.custom_page_sizes = Some(true);
109 ret.multi_memory = Some(true);
110 }
111 Some("exception-handling") => {
112 ret.reference_types = Some(true);
113 }
114 Some("gc") => {
115 ret.gc = Some(true);
116 ret.tail_call = Some(true);
117 }
118 Some("function-references") => {
119 ret.function_references = Some(true);
120 ret.tail_call = Some(true);
121 }
122 Some("annotations") => {
123 ret.simd = Some(true);
124 }
125 Some(proposal) => panic!("unsuported proposal {proposal:?}"),
126 None => {
127 ret.reference_types = Some(true);
128 ret.simd = Some(true);
129 }
130 }
131
132 ret
133}
134
135pub fn parse_test_config<T>(wat: &str) -> Result<T>
138where
139 T: DeserializeOwned,
140{
141 let config_lines: Vec<_> = wat
144 .lines()
145 .take_while(|l| l.starts_with(";;!"))
146 .map(|l| &l[3..])
147 .collect();
148 let config_text = config_lines.join("\n");
149
150 toml::from_str(&config_text).context("failed to parse the test configuration")
151}
152
153#[derive(Clone)]
155pub struct WastTest {
156 pub path: PathBuf,
157 pub contents: String,
158 pub config: TestConfig,
159}
160
161impl fmt::Debug for WastTest {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 f.debug_struct("WastTest")
164 .field("path", &self.path)
165 .field("contents", &"...")
166 .field("config", &self.config)
167 .finish()
168 }
169}
170
171macro_rules! foreach_config_option {
172 ($m:ident) => {
173 $m! {
174 memory64
175 custom_page_sizes
176 multi_memory
177 threads
178 gc
179 function_references
180 relaxed_simd
181 reference_types
182 tail_call
183 extended_const
184 wide_arithmetic
185 hogs_memory
186 nan_canonicalization
187 component_model_async
188 simd
189 gc_types
190 }
191 };
192}
193
194macro_rules! define_test_config {
195 ($($option:ident)*) => {
196 #[derive(Debug, PartialEq, Default, Deserialize, Clone)]
199 #[serde(deny_unknown_fields)]
200 pub struct TestConfig {
201 $(pub $option: Option<bool>,)*
202 }
203
204 impl TestConfig {
205 $(
206 pub fn $option(&self) -> bool {
207 self.$option.unwrap_or(false)
208 }
209 )*
210 }
211
212 }
213}
214
215foreach_config_option!(define_test_config);
216
217impl TestConfig {
218 pub fn options_mut(&mut self) -> impl Iterator<Item = (&'static str, &mut Option<bool>)> {
220 macro_rules! mk {
221 ($($option:ident)*) => {
222 [
223 $((stringify!($option), &mut self.$option),)*
224 ].into_iter()
225 }
226 }
227 foreach_config_option!(mk)
228 }
229}
230
231#[derive(Debug)]
233pub struct WastConfig {
234 pub compiler: Compiler,
236 pub pooling: bool,
238 pub collector: Collector,
240}
241
242#[derive(PartialEq, Debug, Copy, Clone)]
244pub enum Compiler {
245 CraneliftNative,
252
253 Winch,
258
259 CraneliftPulley,
266}
267
268impl Compiler {
269 pub fn should_fail(&self, config: &TestConfig) -> bool {
280 match self {
281 Compiler::CraneliftNative => {}
284
285 Compiler::Winch => {
288 if config.gc()
289 || config.tail_call()
290 || config.function_references()
291 || config.gc()
292 || config.relaxed_simd()
293 || config.gc_types()
294 {
295 return true;
296 }
297 }
298
299 Compiler::CraneliftPulley => {
300 if config.threads() {
304 return true;
305 }
306 }
307 }
308
309 false
310 }
311
312 pub fn supports_host(&self) -> bool {
315 match self {
316 Compiler::CraneliftNative => {
317 cfg!(target_arch = "x86_64")
318 || cfg!(target_arch = "aarch64")
319 || cfg!(target_arch = "riscv64")
320 || cfg!(target_arch = "s390x")
321 }
322 Compiler::Winch => {
323 cfg!(target_arch = "x86_64")
324 }
325 Compiler::CraneliftPulley => true,
326 }
327 }
328}
329
330#[derive(PartialEq, Debug, Copy, Clone)]
331pub enum Collector {
332 Auto,
333 Null,
334 DeferredReferenceCounting,
335}
336
337impl WastTest {
338 pub fn test_uses_gc_types(&self) -> bool {
341 self.config.gc() || self.config.function_references()
342 }
343
344 pub fn spec_proposal(&self) -> Option<&str> {
346 spec_proposal_from_path(&self.path)
347 }
348
349 pub fn should_fail(&self, config: &WastConfig) -> bool {
352 if !config.compiler.supports_host() {
353 return true;
354 }
355
356 if config.pooling {
358 let unsupported = [
359 "misc_testsuite/memory64/more-than-4gb.wast",
361 "misc_testsuite/memory-combos.wast",
363 "misc_testsuite/threads/LB.wast",
364 "misc_testsuite/threads/LB_atomic.wast",
365 "misc_testsuite/threads/MP.wast",
366 "misc_testsuite/threads/MP_atomic.wast",
367 "misc_testsuite/threads/MP_wait.wast",
368 "misc_testsuite/threads/SB.wast",
369 "misc_testsuite/threads/SB_atomic.wast",
370 "misc_testsuite/threads/atomics_notify.wast",
371 "misc_testsuite/threads/atomics_wait_address.wast",
372 "misc_testsuite/threads/wait_notify.wast",
373 "spec_testsuite/proposals/threads/atomic.wast",
374 "spec_testsuite/proposals/threads/exports.wast",
375 "spec_testsuite/proposals/threads/memory.wast",
376 ];
377
378 if unsupported.iter().any(|part| self.path.ends_with(part)) {
379 return true;
380 }
381 }
382
383 if config.compiler.should_fail(&self.config) {
384 return true;
385 }
386
387 if config.compiler == Compiler::Winch {
389 let unsupported = [
390 "component-model/modules.wast",
392 "extended-const/elem.wast",
393 "extended-const/global.wast",
394 "misc_testsuite/externref-id-function.wast",
395 "misc_testsuite/externref-segment.wast",
396 "misc_testsuite/externref-segments.wast",
397 "misc_testsuite/externref-table-dropped-segment-issue-8281.wast",
398 "misc_testsuite/linking-errors.wast",
399 "misc_testsuite/many_table_gets_lead_to_gc.wast",
400 "misc_testsuite/mutable_externref_globals.wast",
401 "misc_testsuite/no-mixup-stack-maps.wast",
402 "misc_testsuite/no-panic.wast",
403 "misc_testsuite/simple_ref_is_null.wast",
404 "misc_testsuite/table_grow_with_funcref.wast",
405 "spec_testsuite/br_table.wast",
406 "spec_testsuite/data-invalid.wast",
407 "spec_testsuite/elem.wast",
408 "spec_testsuite/global.wast",
409 "spec_testsuite/linking.wast",
410 "spec_testsuite/ref_func.wast",
411 "spec_testsuite/ref_is_null.wast",
412 "spec_testsuite/ref_null.wast",
413 "spec_testsuite/select.wast",
414 "spec_testsuite/table_fill.wast",
415 "spec_testsuite/table_get.wast",
416 "spec_testsuite/table_grow.wast",
417 "spec_testsuite/table_set.wast",
418 "spec_testsuite/table_size.wast",
419 "misc_testsuite/simd/canonicalize-nan.wast",
421 ];
422
423 if unsupported.iter().any(|part| self.path.ends_with(part)) {
424 return true;
425 }
426
427 #[cfg(target_arch = "x86_64")]
429 if !(std::is_x86_feature_detected!("avx") && std::is_x86_feature_detected!("avx2")) {
430 let unsupported = [
431 "annotations/simd_lane.wast",
432 "memory64/simd.wast",
433 "misc_testsuite/int-to-float-splat.wast",
434 "misc_testsuite/issue6562.wast",
435 "misc_testsuite/simd/almost-extmul.wast",
436 "misc_testsuite/simd/cvt-from-uint.wast",
437 "misc_testsuite/simd/issue_3327_bnot_lowering.wast",
438 "misc_testsuite/simd/issue6725-no-egraph-panic.wast",
439 "misc_testsuite/simd/replace-lane-preserve.wast",
440 "misc_testsuite/simd/spillslot-size-fuzzbug.wast",
441 "misc_testsuite/winch/issue-10331.wast",
442 "misc_testsuite/winch/replace_lane.wast",
443 "spec_testsuite/simd_align.wast",
444 "spec_testsuite/simd_boolean.wast",
445 "spec_testsuite/simd_conversions.wast",
446 "spec_testsuite/simd_f32x4.wast",
447 "spec_testsuite/simd_f32x4_arith.wast",
448 "spec_testsuite/simd_f32x4_cmp.wast",
449 "spec_testsuite/simd_f32x4_pmin_pmax.wast",
450 "spec_testsuite/simd_f32x4_rounding.wast",
451 "spec_testsuite/simd_f64x2.wast",
452 "spec_testsuite/simd_f64x2_arith.wast",
453 "spec_testsuite/simd_f64x2_cmp.wast",
454 "spec_testsuite/simd_f64x2_pmin_pmax.wast",
455 "spec_testsuite/simd_f64x2_rounding.wast",
456 "spec_testsuite/simd_i16x8_cmp.wast",
457 "spec_testsuite/simd_i32x4_cmp.wast",
458 "spec_testsuite/simd_i64x2_arith2.wast",
459 "spec_testsuite/simd_i64x2_cmp.wast",
460 "spec_testsuite/simd_i8x16_arith2.wast",
461 "spec_testsuite/simd_i8x16_cmp.wast",
462 "spec_testsuite/simd_int_to_int_extend.wast",
463 "spec_testsuite/simd_load.wast",
464 "spec_testsuite/simd_load_extend.wast",
465 "spec_testsuite/simd_load_splat.wast",
466 "spec_testsuite/simd_load_zero.wast",
467 "spec_testsuite/simd_splat.wast",
468 "spec_testsuite/simd_store16_lane.wast",
469 "spec_testsuite/simd_store32_lane.wast",
470 "spec_testsuite/simd_store64_lane.wast",
471 "spec_testsuite/simd_store8_lane.wast",
472 "spec_testsuite/simd_load16_lane.wast",
473 "spec_testsuite/simd_load32_lane.wast",
474 "spec_testsuite/simd_load64_lane.wast",
475 "spec_testsuite/simd_load8_lane.wast",
476 "spec_testsuite/simd_bitwise.wast",
477 "misc_testsuite/simd/load_splat_out_of_bounds.wast",
478 "misc_testsuite/simd/unaligned-load.wast",
479 "multi-memory/simd_memory-multi.wast",
480 "misc_testsuite/simd/issue4807.wast",
481 "spec_testsuite/simd_const.wast",
482 "spec_testsuite/simd_i8x16_sat_arith.wast",
483 "spec_testsuite/simd_i64x2_arith.wast",
484 "spec_testsuite/simd_i16x8_arith.wast",
485 "spec_testsuite/simd_i16x8_arith2.wast",
486 "spec_testsuite/simd_i16x8_q15mulr_sat_s.wast",
487 "spec_testsuite/simd_i16x8_sat_arith.wast",
488 "spec_testsuite/simd_i32x4_arith.wast",
489 "spec_testsuite/simd_i32x4_dot_i16x8.wast",
490 "spec_testsuite/simd_i32x4_trunc_sat_f32x4.wast",
491 "spec_testsuite/simd_i32x4_trunc_sat_f64x2.wast",
492 "spec_testsuite/simd_i8x16_arith.wast",
493 "spec_testsuite/simd_bit_shift.wast",
494 "spec_testsuite/simd_lane.wast",
495 "spec_testsuite/simd_i16x8_extmul_i8x16.wast",
496 "spec_testsuite/simd_i32x4_extmul_i16x8.wast",
497 "spec_testsuite/simd_i64x2_extmul_i32x4.wast",
498 "spec_testsuite/simd_i16x8_extadd_pairwise_i8x16.wast",
499 "spec_testsuite/simd_i32x4_extadd_pairwise_i16x8.wast",
500 "spec_testsuite/simd_i32x4_arith2.wast",
501 ];
502
503 if unsupported.iter().any(|part| self.path.ends_with(part)) {
504 return true;
505 }
506 }
507 }
508
509 for part in self.path.iter() {
510 if part == "exception-handling" {
512 return !self.path.ends_with("binary.wast") && !self.path.ends_with("exports.wast");
513 }
514
515 if part == "memory64" {
516 if [
517 "imports.wast",
519 "ref_null.wast",
520 "throw.wast",
521 "throw_ref.wast",
522 "try_table.wast",
523 "tag.wast",
524 "instance.wast",
525 ]
526 .iter()
527 .any(|i| self.path.ends_with(i))
528 {
529 return true;
530 }
531 }
532 }
533
534 false
535 }
536}
537
538fn spec_proposal_from_path(path: &Path) -> Option<&str> {
539 let mut iter = path.iter();
540 loop {
541 match iter.next()?.to_str()? {
542 "proposals" => break,
543 _ => {}
544 }
545 }
546 Some(iter.next()?.to_str()?)
547}