forked from rustwasm/walrus
-
Notifications
You must be signed in to change notification settings - Fork 0
/
spec-tests.rs
187 lines (168 loc) · 7.25 KB
/
spec-tests.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
use anyhow::Context;
use std::fs;
use std::path::Path;
use std::process::Command;
use tempfile::TempDir;
#[derive(serde::Deserialize, serde::Serialize)]
struct Test {
source_filename: String,
commands: Vec<serde_json::Value>,
}
fn run(wast: &Path) -> Result<(), anyhow::Error> {
static INIT_LOGS: std::sync::Once = std::sync::Once::new();
INIT_LOGS.call_once(|| {
env_logger::init();
});
let proposal = wast
.iter()
.skip_while(|part| *part != "proposals")
.skip(1)
.next()
.map(|s| s.to_str().unwrap());
let extra_args: &[&str] = match proposal {
None => &[],
Some("mutable-global") => &[],
Some("sign-extension-ops") => &["--enable-sign-extension"],
Some("multi-value") => &["--enable-multi-value"],
Some("nontrapping-float-to-int-conversions") => &["--enable-saturating-float-to-int"],
Some("reference-types") => &["--enable-reference-types", "--enable-bulk-memory"],
Some("bulk-memory-operations") => &["--enable-bulk-memory"],
// TODO: should get threads working
Some("threads") => return Ok(()),
// TODO: should get simd working
Some("simd") => return Ok(()),
// TODO: should get tail-call working
Some("tail-call") => return Ok(()),
// Some("threads") => &["--enable-threads"],
Some(other) => panic!("unknown wasm proposal: {}", other),
};
let tempdir = TempDir::new()?;
let json = tempdir.path().join("foo.json");
let status = Command::new("wast2json")
.arg(wast)
.arg("-o")
.arg(&json)
.args(extra_args)
.status()
.context("executing `wast2json`")?;
assert!(status.success());
let contents = fs::read_to_string(&json).context("failed to read file")?;
let test: Test = serde_json::from_str(&contents).context("failed to parse file")?;
let mut files = Vec::new();
let mut config = walrus::ModuleConfig::new();
if extra_args.len() == 0 {
config.only_stable_features(true);
}
for command in test.commands {
let filename = match command.get("filename") {
Some(name) => name.as_str().unwrap().to_string(),
None => continue,
};
let line = &command["line"];
let path = tempdir.path().join(filename);
match command["type"].as_str().unwrap() {
"assert_invalid" | "assert_malformed" => {
// Skip tests that are actually valid with various in-flight proposals
let text = command["text"].as_str().unwrap();
if text == "invalid result arity"
|| text == "multiple memories"
|| text == "multiple tables"
{
continue;
}
let wasm = fs::read(&path)?;
println!("{:?}", command);
if config.parse(&wasm).is_ok() {
// A few spec tests assume multi-value isn't implemented,
// but we implement it, so basically just skip those tests.
let message = command["text"].as_str().unwrap();
if message.contains("invalid result arity") {
continue;
}
// MVP wasm considers this tests to fail, but
// reference-types-enhanced wasm considers this test to
// pass. We implement the reference-types semantics, so
// let's go forward with that.
if wast.ends_with("unreached-invalid.wast") && line == 539 {
continue;
}
panic!("wasm parsed when it shouldn't (line {})", line);
}
}
"assert_unlinkable" if wast.file_name() == Some("elem.wast".as_ref()) => {
// The `elem.wast` file has some unlinkable modules which place
// table elements at massive (aka negative) offsets. Our
// representation means that we try to allocate a massive amount
// of space for null elements. For now we skip these tests as an
// implementation detail. This is arguably a bug on our end
// where we should improve our representation to not allocate so
// much, but that's another bug for another day.
}
cmd => {
let wasm = fs::read(&path)?;
let mut wasm = config
.parse(&wasm)
.context(format!("error parsing wasm (line {})", line))?;
let wasm1 = wasm.emit_wasm();
fs::write(&path, &wasm1)?;
let wasm2 = config
.parse(&wasm1)
.map(|mut m| m.emit_wasm())
.context(format!("error re-parsing wasm (line {})", line))?;
if wasm1 != wasm2 {
panic!("wasm module at line {} isn't deterministic", line);
}
files.push((cmd.to_string(), path.to_path_buf()));
continue;
}
}
}
// First up run the spec-tests as-is after we round-tripped through walrus.
// This should for sure work correctly
run_spectest_interp(tempdir.path(), extra_args)?;
// Next run the same spec tests with semantics-preserving passes implemented
// in walrus. Everything should continue to pass.
for (cmd, file) in files.iter() {
let wasm = fs::read(file)?;
let mut module = config.parse(&wasm)?;
// Tests which assert that they're not linkable tend to not work with
// the gc pass because it removes things which would cause a module to
// become unlinkable. This doesn't matter too much in the real world
// (hopefully), so just don't gc assert_unlinkable modules.
if cmd != "assert_unlinkable" {
walrus::passes::gc::run(&mut module);
}
let wasm = module.emit_wasm();
fs::write(&file, wasm)?;
}
run_spectest_interp(tempdir.path(), extra_args)?;
Ok(())
}
fn run_spectest_interp(cwd: &Path, extra_args: &[&str]) -> Result<(), anyhow::Error> {
let output = Command::new("spectest-interp")
.current_dir(cwd)
.arg("foo.json")
.args(extra_args)
.output()
.context("executing `spectest-interp`")?;
// If the interpreter exits with success it may still have failed some
// tests. Check the output for `X/Y tests passed.` and make sure `X` equals
// `Y`.
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(line) = stdout.lines().find(|l| l.ends_with("tests passed.")) {
let part = line.split_whitespace().next().unwrap();
let mut parts = part.split("/");
let a = parts.next().unwrap().parse::<u32>();
let b = parts.next().unwrap().parse::<u32>();
if a == b {
return Ok(());
}
}
}
println!("status: {}", output.status);
println!("stdout:\n{}", String::from_utf8_lossy(&output.stdout));
println!("stderr:\n{}", String::from_utf8_lossy(&output.stderr));
panic!("failed");
}
include!(concat!(env!("OUT_DIR"), "/spec-tests.rs"));