1use serde::de::DeserializeOwned;
2use serde_derive::Deserialize;
3use std::fmt;
4use std::fs;
5use std::path::Path;
6use std::path::PathBuf;
7use wasmtime_environ::prelude::*;
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
56 {
59 let skip_list = &[
60 ];
62 tests.retain(|test| {
63 test.path
64 .file_name()
65 .and_then(|name| name.to_str())
66 .map(|name| !skip_list.contains(&name))
67 .unwrap_or(true)
68 });
69 }
70
71 Ok(tests)
72}
73
74enum FindConfig {
75 InTest,
76 Infer(fn(&Path) -> TestConfig),
77}
78
79fn add_tests(tests: &mut Vec<WastTest>, path: &Path, config: &FindConfig) -> Result<()> {
80 for entry in path.read_dir().context("failed to read directory")? {
81 let entry = entry.context("failed to read directory entry")?;
82 let path = entry.path();
83 if entry
84 .file_type()
85 .context("failed to get file type")?
86 .is_dir()
87 {
88 add_tests(tests, &path, config).context("failed to read sub-directory")?;
89 continue;
90 }
91
92 if path.extension().and_then(|s| s.to_str()) != Some("wast") {
93 continue;
94 }
95
96 let contents =
97 fs::read_to_string(&path).with_context(|| format!("failed to read test: {path:?}"))?;
98 let config = match config {
99 FindConfig::InTest => parse_test_config(&contents, ";;!")
100 .with_context(|| format!("failed to parse test configuration: {path:?}"))?,
101 FindConfig::Infer(f) => f(&path),
102 };
103 tests.push(WastTest {
104 path,
105 contents,
106 config,
107 })
108 }
109 Ok(())
110}
111
112fn spec_test_config(test: &Path) -> TestConfig {
113 let mut ret = TestConfig::default();
114 ret.spec_test = Some(true);
115 ret.bulk_memory = Some(true);
116 match spec_proposal_from_path(test) {
117 Some("wide-arithmetic") => {
118 ret.wide_arithmetic = Some(true);
119 }
120 Some("threads") => {
121 ret.threads = Some(true);
122 ret.reference_types = Some(false);
123 }
124 Some("custom-page-sizes") => {
125 ret.custom_page_sizes = Some(true);
126 ret.multi_memory = Some(true);
127 ret.memory64 = Some(true);
128
129 if test.ends_with("memory_max.wast") || test.ends_with("memory_max_i64.wast") {
132 ret.hogs_memory = Some(true);
133 }
134 }
135 Some("custom-descriptors") => {
136 ret.custom_descriptors = Some(true);
137 }
138 Some(proposal) => panic!("unsupported proposal {proposal:?}"),
139 None => {
140 ret.reference_types = Some(true);
141 ret.simd = Some(true);
142 ret.simd = Some(true);
143 ret.relaxed_simd = Some(true);
144 ret.multi_memory = Some(true);
145 ret.gc = Some(true);
146 ret.reference_types = Some(true);
147 ret.memory64 = Some(true);
148 ret.tail_call = Some(true);
149 ret.extended_const = Some(true);
150 ret.exceptions = Some(true);
151
152 if test.parent().unwrap().ends_with("legacy") {
153 ret.legacy_exceptions = Some(true);
154 }
155
156 if test.ends_with("memory.wast")
165 || test.ends_with("table.wast")
166 || test.ends_with("memory64.wast")
167 || test.ends_with("table64.wast")
168 {
169 ret.hogs_memory = Some(true);
170 }
171 }
172 }
173
174 ret
175}
176
177fn component_test_config(test: &Path) -> TestConfig {
178 let mut ret = TestConfig::default();
179 ret.spec_test = Some(true);
180 ret.reference_types = Some(true);
181 ret.multi_memory = Some(true);
182
183 if let Some(parent) = test.parent() {
184 if parent.ends_with("async")
185 || [
186 "trap-in-post-return.wast",
187 "resources.wast",
188 "multiple-resources.wast",
189 ]
190 .into_iter()
191 .any(|name| Some(name) == test.file_name().and_then(|s| s.to_str()))
192 {
193 ret.component_model_async = Some(true);
194 ret.component_model_async_stackful = Some(true);
195 ret.component_model_async_builtins = Some(true);
196 ret.component_model_threading = Some(true);
197 }
198 if parent.ends_with("wasm-tools") {
199 ret.memory64 = Some(true);
200 ret.threads = Some(true);
201 ret.exceptions = Some(true);
202 ret.gc = Some(true);
203 }
204 if parent.ends_with("wasmtime") {
205 ret.exceptions = Some(true);
206 ret.gc = Some(true);
207 }
208 }
209
210 ret
211}
212
213pub fn parse_test_config<T>(wat: &str, comment: &'static str) -> Result<T>
216where
217 T: DeserializeOwned,
218{
219 let config_lines: Vec<_> = wat
222 .lines()
223 .take_while(|l| l.starts_with(comment))
224 .map(|l| &l[comment.len()..])
225 .collect();
226 let config_text = config_lines.join("\n");
227
228 toml::from_str(&config_text).context("failed to parse the test configuration")
229}
230
231#[derive(Clone)]
233pub struct WastTest {
234 pub path: PathBuf,
235 pub contents: String,
236 pub config: TestConfig,
237}
238
239impl fmt::Debug for WastTest {
240 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241 f.debug_struct("WastTest")
242 .field("path", &self.path)
243 .field("contents", &"...")
244 .field("config", &self.config)
245 .finish()
246 }
247}
248
249macro_rules! foreach_config_option {
250 ($m:ident) => {
251 $m! {
252 bulk_memory
253 memory64
254 custom_page_sizes
255 multi_memory
256 threads
257 shared_everything_threads
258 gc
259 function_references
260 relaxed_simd
261 reference_types
262 tail_call
263 extended_const
264 wide_arithmetic
265 hogs_memory
266 nan_canonicalization
267 component_model_async
268 component_model_async_builtins
269 component_model_async_stackful
270 component_model_threading
271 component_model_error_context
272 component_model_gc
273 component_model_map
274 component_model_fixed_length_lists
275 simd
276 gc_types
277 exceptions
278 legacy_exceptions
279 stack_switching
280 spec_test
281 custom_descriptors
282 }
283 };
284}
285
286macro_rules! define_test_config {
287 ($($option:ident)*) => {
288 #[derive(Debug, PartialEq, Default, Deserialize, Clone)]
291 #[serde(deny_unknown_fields)]
292 pub struct TestConfig {
293 $(pub $option: Option<bool>,)*
294 }
295
296 impl TestConfig {
297 $(
298 pub fn $option(&self) -> bool {
299 self.$option.unwrap_or(false)
300 }
301 )*
302 }
303 }
304}
305
306foreach_config_option!(define_test_config);
307
308impl TestConfig {
309 pub fn options_mut(&mut self) -> impl Iterator<Item = (&'static str, &mut Option<bool>)> {
311 macro_rules! mk {
312 ($($option:ident)*) => {
313 [
314 $((stringify!($option), &mut self.$option),)*
315 ].into_iter()
316 }
317 }
318 foreach_config_option!(mk)
319 }
320}
321
322#[derive(Debug)]
324pub struct WastConfig {
325 pub compiler: Compiler,
327 pub pooling: bool,
329 pub collector: Collector,
331}
332
333#[derive(PartialEq, Debug, Copy, Clone)]
335pub enum Compiler {
336 CraneliftNative,
343
344 Winch,
349
350 CraneliftPulley,
357}
358
359impl Compiler {
360 pub fn should_fail(&self, config: &TestConfig) -> bool {
371 match self {
372 Compiler::CraneliftNative => {
373 if config.legacy_exceptions() {
374 return true;
375 }
376
377 if config.stack_switching() && !(cfg!(target_arch = "x86_64") && cfg!(unix)) {
380 return true;
381 }
382
383 false
384 }
385
386 Compiler::Winch => {
387 if config.gc()
388 || config.tail_call()
389 || config.function_references()
390 || config.gc()
391 || config.relaxed_simd()
392 || config.gc_types()
393 || config.exceptions()
394 || config.legacy_exceptions()
395 || config.stack_switching()
396 || config.legacy_exceptions()
397 || config.component_model_async()
398 {
399 return true;
400 }
401
402 if cfg!(target_arch = "aarch64") {
403 return config.wide_arithmetic()
404 || (config.simd() && !config.spec_test())
405 || config.threads();
406 }
407
408 !cfg!(target_arch = "x86_64")
409 }
410
411 Compiler::CraneliftPulley => {
412 config.threads() || config.legacy_exceptions() || config.stack_switching()
413 }
414 }
415 }
416
417 pub fn supports_host(&self) -> bool {
420 match self {
421 Compiler::CraneliftNative => {
422 cfg!(target_arch = "x86_64")
423 || cfg!(target_arch = "aarch64")
424 || cfg!(target_arch = "riscv64")
425 || cfg!(target_arch = "s390x")
426 }
427 Compiler::Winch => cfg!(target_arch = "x86_64") || cfg!(target_arch = "aarch64"),
428 Compiler::CraneliftPulley => true,
429 }
430 }
431}
432
433#[derive(PartialEq, Debug, Copy, Clone)]
434pub enum Collector {
435 Auto,
436 Null,
437 DeferredReferenceCounting,
438}
439
440impl WastTest {
441 pub fn test_uses_gc_types(&self) -> bool {
444 self.config.gc() || self.config.function_references()
445 }
446
447 pub fn spec_proposal(&self) -> Option<&str> {
449 spec_proposal_from_path(&self.path)
450 }
451
452 pub fn should_fail(&self, config: &WastConfig) -> bool {
455 if !config.compiler.supports_host() {
456 return true;
457 }
458
459 let unsupported = [
463 "test/async/same-component-stream-future.wast",
464 "test/async/trap-if-block-and-sync.wast",
465 ];
466 if unsupported.iter().any(|part| self.path.ends_with(part)) {
467 return true;
468 }
469
470 if config.pooling {
472 if self.config.hogs_memory() {
474 return true;
475 }
476 let unsupported = [
477 "misc_testsuite/memory-combos.wast",
479 "misc_testsuite/threads/atomics-end-of-memory.wast",
480 "misc_testsuite/threads/LB.wast",
481 "misc_testsuite/threads/LB_atomic.wast",
482 "misc_testsuite/threads/MP.wast",
483 "misc_testsuite/threads/MP_atomic.wast",
484 "misc_testsuite/threads/MP_wait.wast",
485 "misc_testsuite/threads/SB.wast",
486 "misc_testsuite/threads/SB_atomic.wast",
487 "misc_testsuite/threads/atomics_notify.wast",
488 "misc_testsuite/threads/atomics_wait_address.wast",
489 "misc_testsuite/threads/wait_notify.wast",
490 "spec_testsuite/proposals/threads/atomic.wast",
491 "spec_testsuite/proposals/threads/exports.wast",
492 "spec_testsuite/proposals/threads/memory.wast",
493 "misc_testsuite/memory64/threads.wast",
494 ];
495
496 if unsupported.iter().any(|part| self.path.ends_with(part)) {
497 return true;
498 }
499 }
500
501 if config.compiler.should_fail(&self.config) {
502 return true;
503 }
504
505 if config.compiler == Compiler::Winch {
507 let unsupported = [
509 "extended-const/elem.wast",
510 "extended-const/global.wast",
511 "misc_testsuite/component-model/modules.wast",
512 "misc_testsuite/externref-id-function.wast",
513 "misc_testsuite/externref-segment.wast",
514 "misc_testsuite/externref-segments.wast",
515 "misc_testsuite/externref-table-dropped-segment-issue-8281.wast",
516 "misc_testsuite/linking-errors.wast",
517 "misc_testsuite/many_table_gets_lead_to_gc.wast",
518 "misc_testsuite/mutable_externref_globals.wast",
519 "misc_testsuite/no-mixup-stack-maps.wast",
520 "misc_testsuite/no-panic.wast",
521 "misc_testsuite/simple_ref_is_null.wast",
522 ];
523
524 if unsupported.iter().any(|part| self.path.ends_with(part)) {
525 return true;
526 }
527
528 #[cfg(target_arch = "aarch64")]
529 {
530 let unsupported = [
531 "misc_testsuite/int-to-float-splat.wast",
532 "misc_testsuite/issue6562.wast",
533 "misc_testsuite/memory64/simd.wast",
534 "misc_testsuite/simd/almost-extmul.wast",
535 "misc_testsuite/simd/canonicalize-nan.wast",
536 "misc_testsuite/simd/cvt-from-uint.wast",
537 "misc_testsuite/simd/edge-of-memory.wast",
538 "misc_testsuite/simd/interesting-float-splat.wast",
539 "misc_testsuite/simd/issue4807.wast",
540 "misc_testsuite/simd/issue6725-no-egraph-panic.wast",
541 "misc_testsuite/simd/issue_3173_select_v128.wast",
542 "misc_testsuite/simd/issue_3327_bnot_lowering.wast",
543 "misc_testsuite/simd/load_splat_out_of_bounds.wast",
544 "misc_testsuite/simd/replace-lane-preserve.wast",
545 "misc_testsuite/simd/spillslot-size-fuzzbug.wast",
546 "misc_testsuite/simd/sse-cannot-fold-unaligned-loads.wast",
547 "misc_testsuite/simd/unaligned-load.wast",
548 "misc_testsuite/simd/v128-select.wast",
549 "misc_testsuite/winch/issue-10331.wast",
550 "misc_testsuite/winch/issue-10357.wast",
551 "misc_testsuite/winch/issue-10460.wast",
552 "misc_testsuite/winch/replace_lane.wast",
553 "misc_testsuite/winch/simd_multivalue.wast",
554 "misc_testsuite/winch/v128_load_lane_invalid_address.wast",
555 "spec_testsuite/proposals/annotations/simd_lane.wast",
556 "spec_testsuite/proposals/multi-memory/simd_memory-multi.wast",
557 "spec_testsuite/simd_address.wast",
558 "spec_testsuite/simd_align.wast",
559 "spec_testsuite/simd_bit_shift.wast",
560 "spec_testsuite/simd_bitwise.wast",
561 "spec_testsuite/simd_boolean.wast",
562 "spec_testsuite/simd_const.wast",
563 "spec_testsuite/simd_conversions.wast",
564 "spec_testsuite/simd_f32x4.wast",
565 "spec_testsuite/simd_f32x4_arith.wast",
566 "spec_testsuite/simd_f32x4_cmp.wast",
567 "spec_testsuite/simd_f32x4_pmin_pmax.wast",
568 "spec_testsuite/simd_f32x4_rounding.wast",
569 "spec_testsuite/simd_f64x2.wast",
570 "spec_testsuite/simd_f64x2_arith.wast",
571 "spec_testsuite/simd_f64x2_cmp.wast",
572 "spec_testsuite/simd_f64x2_pmin_pmax.wast",
573 "spec_testsuite/simd_f64x2_rounding.wast",
574 "spec_testsuite/simd_i16x8_arith.wast",
575 "spec_testsuite/simd_i16x8_arith2.wast",
576 "spec_testsuite/simd_i16x8_cmp.wast",
577 "spec_testsuite/simd_i16x8_extadd_pairwise_i8x16.wast",
578 "spec_testsuite/simd_i16x8_extmul_i8x16.wast",
579 "spec_testsuite/simd_i16x8_q15mulr_sat_s.wast",
580 "spec_testsuite/simd_i16x8_sat_arith.wast",
581 "spec_testsuite/simd_i32x4_arith.wast",
582 "spec_testsuite/simd_i32x4_arith2.wast",
583 "spec_testsuite/simd_i32x4_cmp.wast",
584 "spec_testsuite/simd_i32x4_dot_i16x8.wast",
585 "spec_testsuite/simd_i32x4_extadd_pairwise_i16x8.wast",
586 "spec_testsuite/simd_i32x4_extmul_i16x8.wast",
587 "spec_testsuite/simd_i32x4_trunc_sat_f32x4.wast",
588 "spec_testsuite/simd_i32x4_trunc_sat_f64x2.wast",
589 "spec_testsuite/simd_i64x2_arith.wast",
590 "spec_testsuite/simd_i64x2_arith2.wast",
591 "spec_testsuite/simd_i64x2_cmp.wast",
592 "spec_testsuite/simd_i64x2_extmul_i32x4.wast",
593 "spec_testsuite/simd_i8x16_arith.wast",
594 "spec_testsuite/simd_i8x16_arith2.wast",
595 "spec_testsuite/simd_i8x16_cmp.wast",
596 "spec_testsuite/simd_i8x16_sat_arith.wast",
597 "spec_testsuite/simd_int_to_int_extend.wast",
598 "spec_testsuite/simd_lane.wast",
599 "spec_testsuite/simd_load.wast",
600 "spec_testsuite/simd_load16_lane.wast",
601 "spec_testsuite/simd_load32_lane.wast",
602 "spec_testsuite/simd_load64_lane.wast",
603 "spec_testsuite/simd_load8_lane.wast",
604 "spec_testsuite/simd_load_extend.wast",
605 "spec_testsuite/simd_load_splat.wast",
606 "spec_testsuite/simd_load_zero.wast",
607 "spec_testsuite/simd_select.wast",
608 "spec_testsuite/simd_splat.wast",
609 "spec_testsuite/simd_store.wast",
610 "spec_testsuite/simd_store16_lane.wast",
611 "spec_testsuite/simd_store32_lane.wast",
612 "spec_testsuite/simd_store64_lane.wast",
613 "spec_testsuite/simd_store8_lane.wast",
614 ];
615
616 if unsupported.iter().any(|part| self.path.ends_with(part)) {
617 return true;
618 }
619 }
620
621 #[cfg(target_arch = "x86_64")]
622 {
623 #[cfg(target_arch = "x86_64")]
625 if !(std::is_x86_feature_detected!("avx") && std::is_x86_feature_detected!("avx2"))
626 {
627 let unsupported = [
628 "annotations/simd_lane.wast",
629 "memory64/simd.wast",
630 "misc_testsuite/int-to-float-splat.wast",
631 "misc_testsuite/issue6562.wast",
632 "misc_testsuite/simd/almost-extmul.wast",
633 "misc_testsuite/simd/canonicalize-nan.wast",
634 "misc_testsuite/simd/cvt-from-uint.wast",
635 "misc_testsuite/simd/edge-of-memory.wast",
636 "misc_testsuite/simd/issue_3327_bnot_lowering.wast",
637 "misc_testsuite/simd/issue6725-no-egraph-panic.wast",
638 "misc_testsuite/simd/replace-lane-preserve.wast",
639 "misc_testsuite/simd/spillslot-size-fuzzbug.wast",
640 "misc_testsuite/simd/sse-cannot-fold-unaligned-loads.wast",
641 "misc_testsuite/winch/issue-10331.wast",
642 "misc_testsuite/winch/replace_lane.wast",
643 "misc_testsuite/simd/riscv64-replicated-imm5-works.wast",
644 "spec_testsuite/simd_align.wast",
645 "spec_testsuite/simd_boolean.wast",
646 "spec_testsuite/simd_conversions.wast",
647 "spec_testsuite/simd_f32x4.wast",
648 "spec_testsuite/simd_f32x4_arith.wast",
649 "spec_testsuite/simd_f32x4_cmp.wast",
650 "spec_testsuite/simd_f32x4_pmin_pmax.wast",
651 "spec_testsuite/simd_f32x4_rounding.wast",
652 "spec_testsuite/simd_f64x2.wast",
653 "spec_testsuite/simd_f64x2_arith.wast",
654 "spec_testsuite/simd_f64x2_cmp.wast",
655 "spec_testsuite/simd_f64x2_pmin_pmax.wast",
656 "spec_testsuite/simd_f64x2_rounding.wast",
657 "spec_testsuite/simd_i16x8_cmp.wast",
658 "spec_testsuite/simd_i32x4_cmp.wast",
659 "spec_testsuite/simd_i64x2_arith2.wast",
660 "spec_testsuite/simd_i64x2_cmp.wast",
661 "spec_testsuite/simd_i8x16_arith2.wast",
662 "spec_testsuite/simd_i8x16_cmp.wast",
663 "spec_testsuite/simd_int_to_int_extend.wast",
664 "spec_testsuite/simd_load.wast",
665 "spec_testsuite/simd_load_extend.wast",
666 "spec_testsuite/simd_load_splat.wast",
667 "spec_testsuite/simd_load_zero.wast",
668 "spec_testsuite/simd_splat.wast",
669 "spec_testsuite/simd_store16_lane.wast",
670 "spec_testsuite/simd_store32_lane.wast",
671 "spec_testsuite/simd_store64_lane.wast",
672 "spec_testsuite/simd_store8_lane.wast",
673 "spec_testsuite/simd_load16_lane.wast",
674 "spec_testsuite/simd_load32_lane.wast",
675 "spec_testsuite/simd_load64_lane.wast",
676 "spec_testsuite/simd_load8_lane.wast",
677 "spec_testsuite/simd_bitwise.wast",
678 "misc_testsuite/simd/load_splat_out_of_bounds.wast",
679 "misc_testsuite/simd/unaligned-load.wast",
680 "multi-memory/simd_memory-multi.wast",
681 "misc_testsuite/simd/issue4807.wast",
682 "spec_testsuite/simd_const.wast",
683 "spec_testsuite/simd_i8x16_sat_arith.wast",
684 "spec_testsuite/simd_i64x2_arith.wast",
685 "spec_testsuite/simd_i16x8_arith.wast",
686 "spec_testsuite/simd_i16x8_arith2.wast",
687 "spec_testsuite/simd_i16x8_q15mulr_sat_s.wast",
688 "spec_testsuite/simd_i16x8_sat_arith.wast",
689 "spec_testsuite/simd_i32x4_arith.wast",
690 "spec_testsuite/simd_i32x4_dot_i16x8.wast",
691 "spec_testsuite/simd_i32x4_trunc_sat_f32x4.wast",
692 "spec_testsuite/simd_i32x4_trunc_sat_f64x2.wast",
693 "spec_testsuite/simd_i8x16_arith.wast",
694 "spec_testsuite/simd_bit_shift.wast",
695 "spec_testsuite/simd_lane.wast",
696 "spec_testsuite/simd_i16x8_extmul_i8x16.wast",
697 "spec_testsuite/simd_i32x4_extmul_i16x8.wast",
698 "spec_testsuite/simd_i64x2_extmul_i32x4.wast",
699 "spec_testsuite/simd_i16x8_extadd_pairwise_i8x16.wast",
700 "spec_testsuite/simd_i32x4_extadd_pairwise_i16x8.wast",
701 "spec_testsuite/simd_i32x4_arith2.wast",
702 ];
703
704 if unsupported.iter().any(|part| self.path.ends_with(part)) {
705 return true;
706 }
707 }
708 }
709 }
710
711 if self.config.custom_descriptors() {
713 let happens_to_work =
714 ["spec_testsuite/proposals/custom-descriptors/binary-leb128.wast"];
715
716 if happens_to_work.iter().any(|part| self.path.ends_with(part)) {
717 return false;
718 }
719 return true;
720 }
721
722 false
723 }
724}
725
726fn spec_proposal_from_path(path: &Path) -> Option<&str> {
727 let mut iter = path.iter();
728 loop {
729 match iter.next()?.to_str()? {
730 "proposals" => break,
731 _ => {}
732 }
733 }
734 Some(iter.next()?.to_str()?)
735}