This commit implements registering unwind information for JIT functions on Windows so that the operating system can both walk and unwind stacks containing JIT frames. Currently this only works with Cranelift as lightbeam does not emit unwind information yet. This commit also resets the stack guard page on Windows for stack overflow exceptions, allowing reliable stack overflow traps. With these changes, all previously disabled test suite tests (not including the multi-value tests) on Windows are now passing. Fixes #291.
240 lines
8.3 KiB
Rust
240 lines
8.3 KiB
Rust
//! Build program to generate a program which runs all the testsuites.
|
|
//!
|
|
//! By generating a separate `#[test]` test for each file, we allow cargo test
|
|
//! to automatically run the files in parallel.
|
|
|
|
use std::env;
|
|
use std::fs::{read_dir, File};
|
|
use std::io::{self, Write};
|
|
use std::path::{Path, PathBuf};
|
|
|
|
fn main() {
|
|
let out_dir =
|
|
PathBuf::from(env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set"));
|
|
let mut out = File::create(out_dir.join("wast_testsuite_tests.rs"))
|
|
.expect("error generating test source file");
|
|
|
|
for strategy in &[
|
|
"Cranelift",
|
|
#[cfg(feature = "lightbeam")]
|
|
"Lightbeam",
|
|
] {
|
|
writeln!(out, "#[cfg(test)]").expect("generating tests");
|
|
writeln!(out, "#[allow(non_snake_case)]").expect("generating tests");
|
|
writeln!(out, "mod {} {{", strategy).expect("generating tests");
|
|
|
|
test_directory(&mut out, "misc_testsuite", strategy).expect("generating tests");
|
|
test_directory(&mut out, "spec_testsuite", strategy).expect("generating tests");
|
|
// Skip running spec_testsuite tests if the submodule isn't checked out.
|
|
if read_dir("spec_testsuite")
|
|
.expect("reading testsuite directory")
|
|
.next()
|
|
.is_some()
|
|
{
|
|
test_file(
|
|
&mut out,
|
|
&to_os_path(&["spec_testsuite", "proposals", "simd", "simd_address.wast"]),
|
|
strategy,
|
|
)
|
|
.expect("generating tests");
|
|
test_file(
|
|
&mut out,
|
|
&to_os_path(&["spec_testsuite", "proposals", "simd", "simd_align.wast"]),
|
|
strategy,
|
|
)
|
|
.expect("generating tests");
|
|
test_file(
|
|
&mut out,
|
|
&to_os_path(&["spec_testsuite", "proposals", "simd", "simd_const.wast"]),
|
|
strategy,
|
|
)
|
|
.expect("generating tests");
|
|
|
|
let multi_value_suite = &to_os_path(&["spec_testsuite", "proposals", "multi-value"]);
|
|
test_directory(&mut out, &multi_value_suite, strategy).expect("generating tests");
|
|
} else {
|
|
println!("cargo:warning=The spec testsuite is disabled. To enable, run `git submodule update --remote`.");
|
|
}
|
|
|
|
writeln!(out, "}}").expect("generating tests");
|
|
}
|
|
}
|
|
|
|
/// Helper for creating OS-independent paths.
|
|
fn to_os_path(components: &[&str]) -> String {
|
|
let path: PathBuf = components.iter().collect();
|
|
path.display().to_string()
|
|
}
|
|
|
|
fn test_directory(out: &mut File, path: &str, strategy: &str) -> io::Result<()> {
|
|
let mut dir_entries: Vec<_> = read_dir(path)
|
|
.expect("reading testsuite directory")
|
|
.map(|r| r.expect("reading testsuite directory entry"))
|
|
.filter(|dir_entry| {
|
|
let p = dir_entry.path();
|
|
if let Some(ext) = p.extension() {
|
|
// Only look at wast files.
|
|
if ext == "wast" {
|
|
// Ignore files starting with `.`, which could be editor temporary files
|
|
if let Some(stem) = p.file_stem() {
|
|
if let Some(stemstr) = stem.to_str() {
|
|
if !stemstr.starts_with('.') {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
false
|
|
})
|
|
.collect();
|
|
|
|
dir_entries.sort_by_key(|dir| dir.path());
|
|
|
|
let testsuite = &extract_name(path);
|
|
start_test_module(out, testsuite)?;
|
|
for dir_entry in dir_entries {
|
|
write_testsuite_tests(out, &dir_entry.path(), testsuite, strategy)?;
|
|
}
|
|
finish_test_module(out)
|
|
}
|
|
|
|
fn test_file(out: &mut File, testfile: &str, strategy: &str) -> io::Result<()> {
|
|
let path = Path::new(testfile);
|
|
let testsuite = format!("single_test_{}", extract_name(path));
|
|
start_test_module(out, &testsuite)?;
|
|
write_testsuite_tests(out, path, &testsuite, strategy)?;
|
|
finish_test_module(out)
|
|
}
|
|
|
|
/// Extract a valid Rust identifier from the stem of a path.
|
|
fn extract_name(path: impl AsRef<Path>) -> String {
|
|
path.as_ref()
|
|
.file_stem()
|
|
.expect("filename should have a stem")
|
|
.to_str()
|
|
.expect("filename should be representable as a string")
|
|
.replace("-", "_")
|
|
.replace("/", "_")
|
|
}
|
|
|
|
fn start_test_module(out: &mut File, testsuite: &str) -> io::Result<()> {
|
|
writeln!(out, " mod {} {{", testsuite)?;
|
|
writeln!(
|
|
out,
|
|
" use super::super::{{native_isa, Path, WastContext, Compiler, Features, CompilationStrategy}};"
|
|
)
|
|
}
|
|
|
|
fn finish_test_module(out: &mut File) -> io::Result<()> {
|
|
writeln!(out, " }}")
|
|
}
|
|
|
|
fn write_testsuite_tests(
|
|
out: &mut File,
|
|
path: &Path,
|
|
testsuite: &str,
|
|
strategy: &str,
|
|
) -> io::Result<()> {
|
|
let testname = extract_name(path);
|
|
|
|
writeln!(out, " #[test]")?;
|
|
if ignore(testsuite, &testname, strategy) {
|
|
writeln!(out, " #[ignore]")?;
|
|
}
|
|
writeln!(out, " fn r#{}() {{", &testname)?;
|
|
writeln!(out, " let isa = native_isa();")?;
|
|
writeln!(
|
|
out,
|
|
" let compiler = Compiler::new(isa, CompilationStrategy::{});",
|
|
strategy
|
|
)?;
|
|
writeln!(
|
|
out,
|
|
" let features = Features {{ simd: {}, multi_value: {}, ..Default::default() }};",
|
|
testsuite.contains("simd"),
|
|
testsuite.contains("multi_value")
|
|
)?;
|
|
writeln!(
|
|
out,
|
|
" let mut wast_context = WastContext::new(Box::new(compiler)).with_features(features);"
|
|
)?;
|
|
writeln!(out, " wast_context")?;
|
|
writeln!(out, " .register_spectest()")?;
|
|
writeln!(
|
|
out,
|
|
" .expect(\"instantiating \\\"spectest\\\"\");"
|
|
)?;
|
|
writeln!(out, " wast_context")?;
|
|
write!(out, " .run_file(Path::new(\"")?;
|
|
// Write out the string with escape_debug to prevent special characters such
|
|
// as backslash from being reinterpreted.
|
|
for c in path.display().to_string().chars() {
|
|
write!(out, "{}", c.escape_debug())?;
|
|
}
|
|
writeln!(out, "\"))")?;
|
|
writeln!(out, " .expect(\"error running wast file\");",)?;
|
|
writeln!(out, " }}")?;
|
|
writeln!(out)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Ignore tests that aren't supported yet.
|
|
fn ignore(testsuite: &str, testname: &str, strategy: &str) -> bool {
|
|
let is_multi_value = testsuite.ends_with("multi_value");
|
|
match strategy {
|
|
#[cfg(feature = "lightbeam")]
|
|
"Lightbeam" => match (testsuite, testname) {
|
|
(_, _) if testname.starts_with("simd") => return true,
|
|
(_, _) if is_multi_value => return true,
|
|
_ => (),
|
|
},
|
|
"Cranelift" => match (testsuite, testname) {
|
|
// We don't currently support more return values than available
|
|
// registers, and this contains a function with many, many more
|
|
// return values than that.
|
|
(_, "func") if is_multi_value => return true,
|
|
_ => {}
|
|
},
|
|
_ => panic!("unrecognized strategy"),
|
|
}
|
|
|
|
if cfg!(windows) {
|
|
return match (testsuite, testname) {
|
|
// Currently, our multi-value support only works with however many
|
|
// extra return registers we have available, and windows' fastcall
|
|
// ABI only has a single return register, so we need to wait on full
|
|
// multi-value support in Cranelift.
|
|
(_, _) if is_multi_value => true,
|
|
(_, _) => false,
|
|
};
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
// Test whether the libc correctly parses the following constant; if so,
|
|
// we can run the "const" test. If not, the "const" test will fail, since
|
|
// we use wabt to parse the tests and wabt uses strtof.
|
|
extern "C" {
|
|
pub fn strtof(s: *const libc::c_char, endp: *mut *mut libc::c_char) -> libc::c_float;
|
|
}
|
|
if unsafe {
|
|
strtof(
|
|
b"8.8817847263968443574e-16" as *const u8 as *const libc::c_char,
|
|
core::ptr::null_mut(),
|
|
)
|
|
}
|
|
.to_bits()
|
|
!= 0x26800001
|
|
{
|
|
return match (testsuite, testname) {
|
|
("spec_testsuite", "const") => true,
|
|
("single_file_spec_test", "simd_const") => true,
|
|
(_, _) => false,
|
|
};
|
|
}
|
|
}
|
|
|
|
false
|
|
}
|