diff options
Diffstat (limited to 'js/src/jit-test/etc/wasm')
18 files changed, 3192 insertions, 0 deletions
diff --git a/js/src/jit-test/etc/wasm/Makefile b/js/src/jit-test/etc/wasm/Makefile new file mode 100644 index 0000000000..c659a7000e --- /dev/null +++ b/js/src/jit-test/etc/wasm/Makefile @@ -0,0 +1,10 @@ +.PHONY: update + +warning = '\# Wasm Spec Tests\n\nThese tests are autogenerated using a tool, do not edit.\n\nSee `jit-test/etc/wasm/` for more information.' + +update: + (cd ./generate-spectests && RUST_BACKTRACE=1 RUST_LOG=info cargo run --release) + rm -r ../../tests/wasm/spec + cp -R generate-spectests/tests/js ../../tests/wasm/spec + echo $(warning) > ../../tests/wasm/spec/README.md + [ -f ./spec-tests.patch ] && (cd ../../tests/wasm/spec && patch -u -p7 < ../../../etc/wasm/spec-tests.patch) diff --git a/js/src/jit-test/etc/wasm/README.md b/js/src/jit-test/etc/wasm/README.md new file mode 100644 index 0000000000..75e4798a88 --- /dev/null +++ b/js/src/jit-test/etc/wasm/README.md @@ -0,0 +1,83 @@ +# Wasm Spec Tests + +This directory contains scripts and configs to manage the in-tree import of the +wasm spec testsuite. + +## Format of tests + +Spec tests are given in `test/core` of the `spec` repository as `.wast` files. +A `.wast` file is a superset of the `.wat` format with commands for running +tests. + +The spec interpreter can natively consume `.wast` files to run the tests, but +we cannot run them directly ourselves. To workaround this, we have a tool which +can convert `.wast` files to `.js` that can be run efficiently in our jit-test +harness. + +## Running tests + +Tests are imported to `jit-test` to be run in the JS shell. + +To run under `jit-test`: +```bash +cd js/src +./jit-test.py path_to_build/dist/bin/js wasm/spec/ +``` + +## Test importing + +There are many proposals in addition to the canonical spec. Each proposal is a +fork of the canonical spec and may modify any amount of files. + +This causes a challenge for any engine implementing multiple proposals, as any +proposal may conflict (and often will) with each other. This makes it +infeasible to merge all spec and proposal repos together. + +Testing each proposal separately in full isn't an attractive option either, as +most tests are unchanged and that would cause a significant amount of wasted +computation time. + +For this reason, we generate a set of separate test-suites that are 'pruned' to +obtain a minimal set of tests. The tool works by merging each proposal with the +proposal it is based off of and removing tests that have not changed. + +### Configuration + +The import tool relies on `config.toml` for the list of proposals to import, +and `config-lock.toml` for a list of commits to use for each proposal. + +The lock file makes test importing deterministic and controllable. This is +useful as proposals often make inconvenient and breaking changes. + +### Operation + +```bash +# Add, remove, or modify proposals +vim generate-spectests/config.toml +# Remove locks for any proposals you wish to pull the latest changes on +vim generate-spectests/config-lock.toml +# Import the tests +make update +# View the tests that were imported +hg stat +# Run the imported tests and note failures +./jit-test.py dist/bin/js wasm/spec/ +# Exclude test failures +vim generate-spectests/config.toml +# Re-import the tests to exclude failing tests +make update +# Commit the changes +hg commit +``` + +### Debugging import failures + +Proposals can often have conflicts with their upstream proposals. This is okay, +and the test importer will fallback to building tests on an unmerged tree. + +This will likely result in extra tests being imported due to spurious +differences between the proposal and upstream, but generally is okay. + +The import tool uses `RUST_LOG` to output debug information. `Makefile` +automatically uses `RUST_LOG=info`. Change that to `RUST_LOG=debug` to get +verbose output of all the commands run. diff --git a/js/src/jit-test/etc/wasm/generate-spectests/.gitignore b/js/src/jit-test/etc/wasm/generate-spectests/.gitignore new file mode 100644 index 0000000000..ca62105090 --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/.gitignore @@ -0,0 +1,2 @@ +specs +tests diff --git a/js/src/jit-test/etc/wasm/generate-spectests/Cargo.lock b/js/src/jit-test/etc/wasm/generate-spectests/Cargo.lock new file mode 100644 index 0000000000..129b13b7f6 --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/Cargo.lock @@ -0,0 +1,262 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "hermit-abi" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "leb128" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" + +[[package]] +name = "libc" +version = "0.2.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5600b4e6efc5421841a2138a6b082e07fe12f9aaa12783d50e5d13325b26b4fc" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "proc-macro2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "serde" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" + +[[package]] +name = "serde_derive" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "wasm-encoder" +version = "0.38.1" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-generate-spectests" +version = "0.1.0" +dependencies = [ + "anyhow", + "env_logger", + "log", + "regex", + "serde", + "serde_derive", + "toml", + "wast2js", +] + +[[package]] +name = "wast" +version = "69.0.1" +dependencies = [ + "leb128", + "memchr", + "unicode-width", + "wasm-encoder", +] + +[[package]] +name = "wast2js" +version = "0.1.0" +dependencies = [ + "anyhow", + "wast", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/js/src/jit-test/etc/wasm/generate-spectests/Cargo.toml b/js/src/jit-test/etc/wasm/generate-spectests/Cargo.toml new file mode 100644 index 0000000000..eb28a53537 --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "wasm-generate-spectests" +version = "0.1.0" +authors = ["Ryan Hunt <rhunt@eqrion.net>"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +regex = "1" +serde = "1" +serde_derive = "1" +toml = "0.5.6" +log = "0.4" +env_logger = "0.7" +anyhow = "1.0.19" +wast2js = { path = "./wast2js" } + +[workspace] diff --git a/js/src/jit-test/etc/wasm/generate-spectests/README.md b/js/src/jit-test/etc/wasm/generate-spectests/README.md new file mode 100644 index 0000000000..18269d6d92 --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/README.md @@ -0,0 +1,64 @@ +# generate-spectests + +A tool to generate a combined testsuite for the wasm spec and all proposals. + +This tool tries to be as robust as possible to deal with upstream breakage, while still generating a minimal testsuite for proposals that don't change many tests. + +## Usage + +NOTE: Running this on its own will generate spec tests but not actually copy them to the jit-test folder or apply patches. If you want to run the whole process for real, use the makefile in the parent folder. + +```bash +# Configure the tests you want +vim config.toml + +# Generate the tests +# This will create a `repos/` and `tests/` in your working directory +cargo run +``` + +## config.toml + +```toml +# (optional) Text to add to a 'directives.txt' file put in 'js/${repo}/harness' +harness_directive = "" + +# (optional) Text to add to a 'directives.txt' file put in 'js/${repo}' +directive = "" + +# (optional) Tests to include even if they haven't changed with respect to their parent repository +included_tests = ["test.wast"] + +# (optional) Tests to exclude +excluded_tests = ["test.wast"] + +[[repos]] +# Name of the repository +name = "sign-extension-ops" + +# Url of the repository +url = "https://github.com/WebAssembly/sign-extension-ops" + +# (optional) Name of the repository that is the upstream for this repository. +# This repository will attempt to merge with this upstream when generating +# tests. The parent repository must be specified before this repository in the +# 'config.toml'. If you change this, you must delete the 'repos' directory +# before generating tests again. +parent = "spec" + +# (optional) Whether to skip merging with upstream, if it exists. +skip_merge = "false" + +# (optional) The commit to checkout when generating tests. If not specified, +# defaults to the latest 'origin/master'. +commit = "df34ea92" + +# (optional) Text to add to a 'directives.txt' file put in 'js/{$repo}' +directive = "" + +# (optional) Tests to include even if they haven't changed with respect to their parent repository +included_tests = ["test.wast"] + +# (optional) Tests to exclude +excluded_tests = ["test.wast"] +``` diff --git a/js/src/jit-test/etc/wasm/generate-spectests/config-lock.toml b/js/src/jit-test/etc/wasm/generate-spectests/config-lock.toml new file mode 100644 index 0000000000..833a05ad15 --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/config-lock.toml @@ -0,0 +1,43 @@ +[[repos]] +name = 'spec' +commit = 'b1fbe1a89' + +[[repos]] +name = 'threads' +commit = '85b562cd' + +[[repos]] +name = 'simd' +commit = 'a78b98a6' + +[[repos]] +name = 'memory64' +commit = '8d8f532d3' + +[[repos]] +name = 'relaxed-simd' +commit = '22257c57b' + +[[repos]] +name = 'extended-const' +commit = '7612271a7' + +[[repos]] +name = 'tail-call' +commit = '6f44ca27a' + +[[repos]] +name = 'multi-memory' +commit = 'c2c00d81c' + +[[repos]] +name = 'function-references' +commit = 'eab6b36ca' + +[[repos]] +name = 'exception-handling' +commit = '98e4eb60d' + +[[repos]] +name = 'gc' +commit = '207333efd' diff --git a/js/src/jit-test/etc/wasm/generate-spectests/config.toml b/js/src/jit-test/etc/wasm/generate-spectests/config.toml new file mode 100644 index 0000000000..48e5be6e0d --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/config.toml @@ -0,0 +1,130 @@ +# Standard 'directives.txt' prologues for jit-tests +harness_directive = "|jit-test| skip-if: true" +directive = "|jit-test| test-also=--wasm-compiler=optimizing; test-also=--wasm-compiler=baseline; test-also=--wasm-test-serialization; test-also=--test-wasm-await-tier2; test-also=--disable-wasm-huge-memory; skip-variant-if: --disable-wasm-huge-memory, !wasmHugeMemorySupported(); local-include:harness/harness.js" + +# Failing tests across all testsuites +excluded_tests = [ + # false-positive windows-specific failures + "align.wast", + # bulk-memory-operations/issues/133 (data.wast:161) + "data.wast", + # memory limits can be encoded with more bytes now + "binary.wast", + "binary-leb128.wast", + # testing that multiple tables fail (imports.wast:309) + "imports.wast", + # bulk-memory-operations/issues/133 (linking.wast:206) + "linking.wast", + # bulk-memory-operations/issues/133 (elem.wast:142) + "elem.wast", + # test harness doesn't acquire global values correctly + "exports.wast", + # false-positive windows-specific failure + "memory_trap.wast", + # false-positive error on invalid UTF-8 custom section name (utf8-custom-section-id.wast:6) + "utf8-custom-section-id.wast", + # invalid table maximum length for web embeddings + "table.wast", + # fails after a bottom-type has been added to validation + "unreached-invalid.wast", + # argument is not wasm value + "^select.wast", +] + +[[repos]] +name = "spec" +url = "https://github.com/WebAssembly/spec" +excluded_tests = [] +directive = "; test-also=--no-avx; skip-variant-if: --no-avx, !getBuildConfiguration('x86') && !getBuildConfiguration('x64') || getBuildConfiguration('simulator')" + +[[repos]] +name = "exception-handling" +url = "https://github.com/WebAssembly/exception-handling" +branch = "main" +parent = "spec" +# Skip in jit-test when it's not enabled +directive = "; --wasm-exceptions; --wasm-exnref; skip-if: !wasmExceptionsEnabled()" +excluded_tests = [ + # harness doesn't support exnref, because JS-API globals can't use it + "ref_null.wast.js" +] + +[[repos]] +name = "memory64" +url = "https://github.com/mozilla-spidermonkey/memory64" +branch = "test-cases" +directive = "; skip-if: !wasmMemory64Enabled()" +excluded_tests = [] + +[[repos]] +name = "function-references" +url = "https://github.com/WebAssembly/function-references" +branch = "main" +parent = "spec" +directive = "; --wasm-function-references; skip-if: !wasmFunctionReferencesEnabled()" +excluded_tests = [ + # duplicate tail calls tests + "return_call.wast", + "return_call_indirect.wast", + # table function elem subtyping + "ref_is_null.wast", + # cannot expose indexed reference type + "ref_null.wast", + # parameter subtyping + "type-equivalence.wast", + # NYI WasmValType.h:259 + "table-sub.wast", + # unrelated + "tokens.wast", + # irrelevant + "simd_lane.wast", + # globals are of different type + "local_init.wast", +] + +[[repos]] +name = "relaxed-simd" +url = "https://github.com/WebAssembly/relaxed-simd" +branch = "main" +parent = "spec" +directive = "; --wasm-relaxed-simd; skip-if: !wasmRelaxedSimdEnabled()" +excluded_tests = [] + +[[repos]] +name = "extended-const" +url = "https://github.com/WebAssembly/extended-const" +branch = "main" +parent = "spec" +directive = "; --wasm-extended-const; --no-wasm-gc; skip-if: !wasmExtendedConstEnabled()" +excluded_tests = [] + +[[repos]] +name = "tail-call" +url = "https://github.com/WebAssembly/tail-call" +branch = "main" +parent = "spec" +directive = "; --wasm-tail-calls; skip-if: !wasmTailCallsEnabled()" +excluded_tests = [] + +[[repos]] +name = "multi-memory" +url = "https://github.com/WebAssembly/multi-memory" +branch = "main" +parent = "spec" +directive = "; --wasm-multi-memory; skip-if: !wasmMultiMemoryEnabled()" +excluded_tests = [ + # Empty test fails parsing + "memory_copy1.wast", +] + +[[repos]] +name = "gc" +url = "https://github.com/WebAssembly/gc" +branch = "main" +parent = "function-references" +directive = "; --wasm-gc; skip-if: !wasmGcEnabled()" +excluded_tests = [ + # tail call tests that snuck in + "return_call.wast", + "return_call_indirect.wast", +] diff --git a/js/src/jit-test/etc/wasm/generate-spectests/src/main.rs b/js/src/jit-test/etc/wasm/generate-spectests/src/main.rs new file mode 100644 index 0000000000..40f27afcc4 --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/src/main.rs @@ -0,0 +1,490 @@ +use std::env; +use std::ffi::OsStr; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +use anyhow::{bail, Result}; +use regex::{RegexSet, RegexSetBuilder}; +use serde_derive::{Deserialize, Serialize}; +use toml; +use wast2js; + +use log::{debug, info, warn}; + +// Data structures + +#[derive(Debug, Default, Serialize, Deserialize)] +struct Config { + #[serde(default)] + harness_directive: Option<String>, + #[serde(default)] + directive: Option<String>, + #[serde(default)] + included_tests: Vec<String>, + #[serde(default)] + excluded_tests: Vec<String>, + repos: Vec<Repo>, +} + +impl Config { + fn find_repo_mut(&mut self, name: &str) -> Option<&mut Repo> { + self.repos.iter_mut().find(|x| &x.name == name) + } +} + +#[derive(Debug, Default, Serialize, Deserialize)] +struct Repo { + name: String, + url: String, + #[serde(default)] + branch: Option<String>, + #[serde(default)] + parent: Option<String>, + #[serde(default)] + directive: Option<String>, + #[serde(default)] + included_tests: Vec<String>, + #[serde(default)] + excluded_tests: Vec<String>, + #[serde(default)] + skip_wast: bool, + #[serde(default)] + skip_js: bool, +} + +#[derive(Debug, Default, Serialize, Deserialize)] +struct Lock { + repos: Vec<LockRepo>, +} + +impl Lock { + fn find_commit(&self, name: &str) -> Option<&str> { + self.repos + .iter() + .find(|x| &x.name == name) + .map(|x| x.commit.as_ref()) + } + + fn set_commit(&mut self, name: &str, commit: &str) { + if let Some(lock) = self.repos.iter_mut().find(|x| &x.name == name) { + lock.commit = commit.to_owned(); + } else { + self.repos.push(LockRepo { + name: name.to_owned(), + commit: commit.to_owned(), + }); + } + } +} + +#[derive(Debug, Default, Serialize, Deserialize)] +struct LockRepo { + name: String, + commit: String, +} + +#[derive(Debug)] +enum Merge { + Standalone, + Merged, + Conflicted, +} + +#[derive(Debug)] +struct Status { + commit_base_hash: String, + commit_final_message: String, + merged: Merge, + built: bool, +} + +// Roll-your-own CLI utilities + +fn run(name: &str, args: &[&str]) -> Result<String> { + debug!("{} {:?}", name, args); + let output = Command::new(name).args(args).output()?; + let stdout = String::from_utf8(output.stdout)?.trim().to_owned(); + let stderr = String::from_utf8(output.stderr)?.trim().to_owned(); + if !stdout.is_empty() { + debug!("{}", stdout); + } + if !stderr.is_empty() { + debug!("{}", stderr); + } + + if output.status.success() { + Ok(stdout) + } else { + bail!("{}: {}\n{}", name.to_owned(), stdout, stderr) + } +} + +fn change_dir(dir: &str) -> impl Drop { + #[must_use] + struct Reset { + previous: PathBuf, + } + impl Drop for Reset { + fn drop(&mut self) { + debug!("cd {}", self.previous.display()); + env::set_current_dir(&self.previous).unwrap() + } + } + + let previous = Reset { + previous: env::current_dir().unwrap(), + }; + debug!("cd {}", dir); + env::set_current_dir(dir).unwrap(); + previous +} + +fn find(dir: &str) -> Vec<PathBuf> { + let mut paths = Vec::new(); + + fn find(dir: &str, paths: &mut Vec<PathBuf>) { + for entry in fs::read_dir(dir).unwrap().map(|x| x.unwrap()) { + let path = entry.path(); + + if entry.file_type().unwrap().is_dir() { + find(path.to_str().unwrap(), paths); + } else { + paths.push(path); + } + } + } + + find(dir, &mut paths); + paths +} + +fn write_string<P: AsRef<Path>>(path: P, text: &str) -> Result<()> { + let path = path.as_ref(); + if let Some(dir) = path.parent() { + let _ = fs::create_dir_all(dir); + } + fs::write(path, text.as_bytes())?; + Ok(()) +} + +// The main script + +fn main() { + env_logger::init(); + + // Load the config + let mut config: Config = + toml::from_str(&fs::read_to_string("config.toml").expect("failed to read config.toml")) + .expect("invalid config.toml"); + + // Load the lock file, or default to no pinned commits + let mut lock: Lock = if Path::new("config-lock.toml").exists() { + toml::from_str( + &fs::read_to_string("config-lock.toml").expect("failed to read config-lock.toml"), + ) + .expect("invalid config-lock.toml") + } else { + Lock::default() + }; + + // Clean old tests and initialize the repo if it doesn't exist + let specs_dir = "specs/"; + clean_and_init_dirs(specs_dir); + + // Generate the tests + let mut successes = Vec::new(); + let mut failures = Vec::new(); + { + // Change to the `specs/` dir where all the work happens + let _cd = change_dir(specs_dir); + for repo in &config.repos { + info!("Processing {:#?}", repo); + + match build_repo(repo, &config, &lock) { + Ok(status) => successes.push((repo.name.clone(), status)), + Err(err) => failures.push((repo.name.clone(), err)), + }; + } + } + + // Abort if we had a failure + if !failures.is_empty() { + warn!("Failed."); + for (name, err) in &failures { + warn!("{}: (failure) {:?}", name, err); + } + std::process::exit(1); + } + + // Display successful results + info!("Done."); + for (name, status) in &successes { + let repo = config.find_repo_mut(&name).unwrap(); + lock.set_commit(&name, &status.commit_base_hash); + + info!( + "{}: ({} {}) {}", + repo.name, + match status.merged { + Merge::Standalone => "standalone", + Merge::Merged => "merged", + Merge::Conflicted => "conflicted", + }, + if status.built { "building" } else { "broken" }, + status.commit_final_message.trim_end() + ); + } + + // Commit the new lock file + write_string("config-lock.toml", &toml::to_string_pretty(&lock).unwrap()).unwrap(); +} + +fn clean_and_init_dirs(specs_dir: &str) { + if !Path::new(specs_dir).exists() { + fs::create_dir(specs_dir).unwrap(); + run("git", &["-C", specs_dir, "init"]).unwrap(); + } + + let _ = fs::remove_dir_all("./tests"); +} + +fn build_repo(repo: &Repo, config: &Config, lock: &Lock) -> Result<Status> { + let remote_name = &repo.name; + let remote_url = &repo.url; + let remote_branch = repo.branch.as_ref().map(|x| x.as_str()).unwrap_or("master"); + let branch_upstream = format!("{}/{}", repo.name, remote_branch); + let branch_base = repo.name.clone(); + + // Initialize our remote and branches if they don't exist + let remotes = run("git", &["remote"])?; + if !remotes.lines().any(|x| x == repo.name) { + run("git", &["remote", "add", remote_name, &remote_url])?; + run("git", &["fetch", remote_name])?; + run("git", &["branch", &branch_base, &branch_upstream])?; + } + + // Set the upstream to the correct branch + run( + "git", + &[ + "branch", + &branch_base, + "--set-upstream-to", + &branch_upstream, + ], + )?; + + // Fetch the latest changes for this repo + run("git", &["fetch", remote_name])?; + + // Checkout the pinned commit, if any, and get the absolute commit hash + let base_treeish = lock.find_commit(&repo.name).unwrap_or(&branch_upstream); + run("git", &["checkout", &branch_base])?; + run("git", &["reset", base_treeish, "--hard"])?; + let commit_base_hash = run("git", &["log", "--pretty=%h", "-n", "1"])? + .trim() + .to_owned(); + + // Try to merge with parent repo, if specified + let merged = try_merge_parent(repo, &commit_base_hash)?; + + // Exclude files specified from the config and repo + let mut excluded_files = Vec::new(); + excluded_files.extend_from_slice(&config.excluded_tests); + excluded_files.extend_from_slice(&repo.excluded_tests); + let exclude = RegexSetBuilder::new(&excluded_files).build().unwrap(); + + // Try to build the test suite on this commit. This may fail due to merging + // with a parent repo, in which case we will try again in an unmerged state. + let mut built = false; + match try_build_tests(&exclude) { + Ok(()) => built = true, + Err(err) => warn!("Failed to build tests: {:?}", err), + }; + // if try_build_tests(&exclude).is_err() { + // if repo.parent.is_some() { + // warn!( + // "Failed to build interpreter. Retrying on unmerged commit ({})", + // &commit_base_hash + // ); + // run("git", &["reset", &commit_base_hash, "--hard"])?; + // built = try_build_tests(&exclude).is_ok(); + // } else { + // built = false; + // } + // } + // if !built { + // warn!("Failed to build interpreter, Won't emit js/html"); + // } + + // Get the final commit message we ended up on + let commit_final_message = run("git", &["log", "--oneline", "-n", "1"])?; + + // Compute the source files that changed, and use that to filter the files + // we copy over. We can't compare the generated tests, because for a + // generated WPT we need to copy both the .js and .html even if only + // one of those is different from the master. + let tests_changed = find_tests_changed(repo)?; + info!("Changed tests: {:#?}", tests_changed); + + // Include the changed tests, specified files, and `harness/` directory + let mut included_files = Vec::new(); + included_files.extend_from_slice(&tests_changed); + included_files.extend_from_slice(&config.included_tests); + included_files.extend_from_slice(&repo.included_tests); + included_files.push("harness/".to_owned()); + let include = RegexSetBuilder::new(&included_files).build().unwrap(); + + // Copy over all the desired test-suites + if !repo.skip_wast { + copy_tests(repo, "test/core", "../tests", "wast", &include, &exclude); + } + if built && !repo.skip_js { + copy_tests(repo, "js", "../tests", "js", &include, &exclude); + copy_directives(repo, config)?; + } + + Ok(Status { + commit_final_message, + commit_base_hash, + merged, + built, + }) +} + +fn try_merge_parent(repo: &Repo, commit_base_hash: &str) -> Result<Merge> { + if !repo.parent.is_some() { + return Ok(Merge::Standalone); + } + let parent = repo.parent.as_ref().unwrap(); + + // Try to merge with the parent branch. + let message = format!("Merging {}:{}with {}", repo.name, commit_base_hash, parent); + Ok( + if !run("git", &["merge", "-q", parent, "-m", &message]).is_ok() { + // Ignore merge conflicts in the document directory. + if !run("git", &["checkout", "--ours", "document"]).is_ok() + || !run("git", &["add", "document"]).is_ok() + || !run("git", &["-c", "core.editor=true", "merge", "--continue"]).is_ok() + { + // Reset to master if we failed + warn!( + "Failed to merge {}, falling back to {}.", + repo.name, &commit_base_hash + ); + run("git", &["merge", "--abort"])?; + run("git", &["reset", &commit_base_hash, "--hard"])?; + Merge::Conflicted + } else { + Merge::Merged + } + } else { + Merge::Merged + }, + ) +} + +fn try_build_tests(exclude: &RegexSet) -> Result<()> { + let _ = fs::remove_dir_all("./js"); + fs::create_dir("./js")?; + + let paths = find("./test/core/"); + for path in paths { + if path.extension() != Some(OsStr::new("wast")) { + continue; + } + + let stripped_path = path.strip_prefix("./test/core/").unwrap(); + let stripped_path_str = stripped_path.to_str().unwrap(); + if exclude.is_match(stripped_path_str) { + continue; + } + + let source = std::fs::read_to_string(&path)?; + let script = wast2js::convert(&path, &source)?; + + std::fs::write( + Path::new("./js").join(&path.with_extension("wast.js").file_name().unwrap()), + &script, + )?; + } + + fs::create_dir("./js/harness")?; + write_string("./js/harness/harness.js", &wast2js::harness())?; + + Ok(()) +} + +fn copy_tests( + repo: &Repo, + src_dir: &str, + dst_dir: &str, + test_name: &str, + include: &RegexSet, + exclude: &RegexSet, +) { + for path in find(src_dir) { + let stripped_path = path.strip_prefix(src_dir).unwrap(); + let stripped_path_str = stripped_path.to_str().unwrap(); + + if !include.is_match(stripped_path_str) || exclude.is_match(stripped_path_str) { + continue; + } + + let out_path = Path::new(dst_dir) + .join(test_name) + .join(&repo.name) + .join(&stripped_path); + let out_dir = out_path.parent().unwrap(); + let _ = fs::create_dir_all(out_dir); + fs::copy(path, out_path).unwrap(); + } +} + +fn copy_directives(repo: &Repo, config: &Config) -> Result<()> { + // Write directives files + if let Some(harness_directive) = &config.harness_directive { + let directives_path = Path::new("../tests/js") + .join(&repo.name) + .join("harness/directives.txt"); + write_string(&directives_path, harness_directive)?; + } + let directives = format!( + "{}{}", + config.directive.as_ref().map(|x| x.as_str()).unwrap_or(""), + repo.directive.as_ref().map(|x| x.as_str()).unwrap_or("") + ); + if !directives.is_empty() { + let directives_path = Path::new("../tests/js") + .join(&repo.name) + .join("directives.txt"); + write_string(&directives_path, &directives)?; + } + Ok(()) +} + +fn find_tests_changed(repo: &Repo) -> Result<Vec<String>> { + let files_changed = if let Some(parent) = repo.parent.as_ref() { + run( + "git", + &["diff", "--name-only", &repo.name, &parent, "test/core"], + )? + .lines() + .map(|x| PathBuf::from(x)) + .collect() + } else { + find("test/core") + }; + + let mut tests_changed = Vec::new(); + for path in files_changed { + if path.extension().map(|x| x.to_str().unwrap()) != Some("wast") { + continue; + } + + let name = path.file_name().unwrap().to_str().unwrap().to_owned(); + tests_changed.push(name); + } + Ok(tests_changed) +} diff --git a/js/src/jit-test/etc/wasm/generate-spectests/wast2js/Cargo.toml b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/Cargo.toml new file mode 100644 index 0000000000..b55ae458df --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "wast2js" +version = "0.1.0" +authors = ["Ryan Hunt <rhunt@eqrion.net>"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.19" +wast = { path = "../../../../../../../../wasm-tools/crates/wast" } diff --git a/js/src/jit-test/etc/wasm/generate-spectests/wast2js/README.md b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/README.md new file mode 100644 index 0000000000..c06210bb04 --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/README.md @@ -0,0 +1,3 @@ +# wast2js + +Mozilla specific converter from `.wast` to `.js` for SpiderMonkey jit-tests. diff --git a/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/convert.rs b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/convert.rs new file mode 100644 index 0000000000..888b049036 --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/convert.rs @@ -0,0 +1,720 @@ +/* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use anyhow::{bail, Context as _, Result}; +use std::fmt::Write; +use std::path::Path; +use std::str; + +use super::out::*; + +const HARNESS: &'static str = include_str!("./harness.js"); +const LICENSE: &'static str = include_str!("./license.js"); + +pub fn harness() -> String { + HARNESS.to_string() +} + +pub fn convert<P: AsRef<Path>>(path: P, wast: &str) -> Result<String> { + let filename = path.as_ref(); + let adjust_wast = |mut err: wast::Error| { + err.set_path(filename); + err.set_text(wast); + err + }; + + let mut lexer = wast::lexer::Lexer::new(wast); + // The 'names.wast' spec test has confusable unicode -- disable detection. + lexer.allow_confusing_unicode(filename.ends_with("names.wast")); + let buf = wast::parser::ParseBuffer::new_with_lexer(lexer).map_err(adjust_wast)?; + let ast = wast::parser::parse::<wast::Wast>(&buf).map_err(adjust_wast)?; + + let mut out = String::new(); + + writeln!(&mut out, "{}", LICENSE)?; + writeln!(&mut out, "// {}", filename.display())?; + + let mut current_instance: Option<usize> = None; + + for directive in ast.directives { + let sp = directive.span(); + let (line, col) = sp.linecol_in(wast); + writeln!(&mut out, "")?; + convert_directive( + directive, + &mut current_instance, + filename, + line, + col, + wast, + &mut out, + ) + .with_context(|| { + format!( + "failed to convert directive on {}:{}:{}", + filename.display(), + line + 1, + col + ) + })?; + } + + Ok(out) +} + +fn convert_directive( + directive: wast::WastDirective, + current_instance: &mut Option<usize>, + filename: &Path, + line: usize, + col: usize, + wast: &str, + out: &mut String, +) -> Result<()> { + use wast::WastDirective::*; + + if col == 1 { + writeln!(out, "// {}:{}", filename.display(), line + 1)?; + } else { + writeln!(out, "// {}:{}:{}", filename.display(), line + 1, col)?; + } + match directive { + Wat(wast::QuoteWat::Wat(wast::Wat::Module(module))) => { + let next_instance = current_instance.map(|x| x + 1).unwrap_or(0); + let module_text = module_to_js_string(&module, wast)?; + + writeln!( + out, + "let ${} = instantiate(`{}`);", + next_instance, module_text + )?; + if let Some(id) = module.id { + writeln!( + out, + "register(${}, {});", + next_instance, + format!("`{}`", escape_template_name_string(id.name())) + )?; + } + + *current_instance = Some(next_instance); + } + Wat(wast::QuoteWat::QuoteModule(_, source)) => { + let next_instance = current_instance.map(|x| x + 1).unwrap_or(0); + let module_text = quote_module_to_js_string(source)?; + + writeln!( + out, + "let ${} = instantiate(`{}`);", + next_instance, module_text + )?; + + *current_instance = Some(next_instance); + } + Wat(_) => { + write!(out, "// unsupported quote wat")?; + } + Register { + span: _, + name, + module, + } => { + let instanceish = module + .map(|x| format!("`{}`", escape_template_name_string(x.name()))) + .unwrap_or_else(|| format!("${}", current_instance.unwrap())); + + writeln!( + out, + "register({}, `{}`);", + instanceish, + escape_template_name_string(name) + )?; + } + Invoke(i) => { + let invoke_node = invoke_to_js(current_instance, i)?; + writeln!(out, "{};", invoke_node.output(0))?; + } + AssertReturn { + span: _, + exec, + results, + } => { + let exec_node = execute_to_js(current_instance, exec, wast)?; + let expected_node = to_js_value_array(&results, assert_expression_to_js_value)?; + writeln!( + out, + "{};", + JSNode::Assert { + name: "assert_return".to_string(), + exec: exec_node, + expected: expected_node, + } + .output(0), + )?; + } + AssertTrap { + span: _, + exec, + message, + } => { + let exec_node = execute_to_js(current_instance, exec, wast)?; + let expected_node = Box::new(JSNode::Raw(format!( + "`{}`", + escape_template_name_string(message) + ))); + writeln!( + out, + "{};", + JSNode::Assert { + name: "assert_trap".to_string(), + exec: exec_node, + expected: expected_node, + } + .output(0), + )?; + } + AssertExhaustion { + span: _, + call, + message, + } => { + let exec_node = invoke_to_js(current_instance, call)?; + let expected_node = Box::new(JSNode::Raw(format!( + "`{}`", + escape_template_name_string(message) + ))); + writeln!( + out, + "{};", + JSNode::Assert { + name: "assert_exhaustion".to_string(), + exec: exec_node, + expected: expected_node, + } + .output(0), + )?; + } + AssertInvalid { + span: _, + module, + message, + } => { + let text = match module { + wast::QuoteWat::Wat(wast::Wat::Module(m)) => module_to_js_string(&m, wast)?, + wast::QuoteWat::QuoteModule(_, source) => quote_module_to_js_string(source)?, + other => bail!("unsupported {:?} in assert_invalid", other), + }; + let exec = Box::new(JSNode::Raw(format!("instantiate(`{}`)", text))); + let expected_node = Box::new(JSNode::Raw(format!( + "`{}`", + escape_template_name_string(message) + ))); + writeln!( + out, + "{};", + JSNode::Assert { + name: "assert_invalid".to_string(), + exec: exec, + expected: expected_node, + } + .output(0), + )?; + } + AssertMalformed { + module, + span: _, + message, + } => { + let text = match module { + wast::QuoteWat::Wat(wast::Wat::Module(m)) => module_to_js_string(&m, wast)?, + wast::QuoteWat::QuoteModule(_, source) => quote_module_to_js_string(source)?, + other => bail!("unsupported {:?} in assert_malformed", other), + }; + let exec = Box::new(JSNode::Raw(format!("instantiate(`{}`)", text))); + let expected_node = Box::new(JSNode::Raw(format!( + "`{}`", + escape_template_name_string(message) + ))); + writeln!( + out, + "{};", + JSNode::Assert { + name: "assert_malformed".to_string(), + exec: exec, + expected: expected_node, + } + .output(0), + )?; + } + AssertUnlinkable { + span: _, + module, + message, + } => { + let text = match module { + wast::Wat::Module(module) => module_to_js_string(&module, wast)?, + other => bail!("unsupported {:?} in assert_unlinkable", other), + }; + let exec = Box::new(JSNode::Raw(format!("instantiate(`{}`)", text))); + let expected_node = Box::new(JSNode::Raw(format!( + "`{}`", + escape_template_name_string(message) + ))); + writeln!( + out, + "{};", + JSNode::Assert { + name: "assert_unlinkable".to_string(), + exec: exec, + expected: expected_node, + } + .output(0), + )?; + } + AssertException { span: _, exec } => { + // This assert doesn't have a second parameter, so we don't bother + // formatting it using an Assert node. + let exec_node = execute_to_js(current_instance, exec, wast)?; + writeln!(out, "assert_exception(() => {});", exec_node.output(0))?; + } + Thread(..) => unimplemented!(), + Wait { .. } => unimplemented!(), + } + + Ok(()) +} + +fn escape_template_string(text: &str, escape_ascii_lf_tab: bool) -> String { + let mut escaped = String::new(); + for c in text.chars() { + match c { + '$' => escaped.push_str("$$"), + '\\' => escaped.push_str("\\\\"), + '`' => escaped.push_str("\\`"), + c if c.is_ascii_control() && escape_ascii_lf_tab && c != '\n' && c != '\t' => { + escaped.push_str(&format!("\\x{:02x}", c as u32)) + } + c if !c.is_ascii() => escaped.push_str(&c.escape_unicode().to_string()), + c => escaped.push(c), + } + } + escaped +} + +// Escape a module for use in a JS template string. +fn escape_template_module_string(text: &str) -> String { + // Modules may have comments, where line-feeds must be passed through as-is + // to preserve their meaning + escape_template_string(text, false) +} + +// Escape a name for use in a JS template string. +fn escape_template_name_string(text: &str) -> String { + // Names don't care about line-feeds or tabs, just need equality + escape_template_string(text, true) +} + +fn span_to_offset(span: wast::token::Span, text: &str) -> Result<usize> { + let (span_line, span_col) = span.linecol_in(text); + let mut cur = 0; + // Use split_terminator instead of lines so that if there is a `\r`, + // it is included in the offset calculation. The `+1` values below + // account for the `\n`. + for (i, line) in text.split_terminator('\n').enumerate() { + if span_line == i { + assert!(span_col < line.len()); + return Ok(cur + span_col); + } + cur += line.len() + 1; + } + bail!("invalid line/col"); +} + +fn closed_module(module: &str) -> Result<&str> { + enum State { + Module, + QStr, + EscapeQStr, + } + + let mut i = 0; + let mut level = 1; + let mut state = State::Module; + + let mut chars = module.chars(); + while level != 0 { + let next = match chars.next() { + Some(next) => next, + None => bail!("was unable to close module"), + }; + match state { + State::Module => match next { + '(' => level += 1, + ')' => level -= 1, + '"' => state = State::QStr, + _ => {} + }, + State::QStr => match next { + '"' => state = State::Module, + '\\' => state = State::EscapeQStr, + _ => {} + }, + State::EscapeQStr => match next { + _ => state = State::QStr, + }, + } + i += next.len_utf8(); + } + return Ok(&module[0..i]); +} + +fn module_to_js_string(module: &wast::core::Module, wast: &str) -> Result<String> { + let offset = span_to_offset(module.span, wast)?; + let opened_module = &wast[offset..]; + if !opened_module.starts_with("module") { + return Ok(escape_template_module_string(opened_module)); + } + Ok(escape_template_module_string(&format!( + "({}", + closed_module(opened_module)? + ))) +} + +fn quote_module_to_js_string(quotes: Vec<(wast::token::Span, &[u8])>) -> Result<String> { + let mut text = String::new(); + for (_, src) in quotes { + text.push_str(str::from_utf8(src)?); + text.push_str(" "); + } + let escaped = escape_template_module_string(&text); + Ok(escaped) +} + +fn invoke_to_js(current_instance: &Option<usize>, i: wast::WastInvoke) -> Result<Box<JSNode>> { + let instanceish = i + .module + .map(|x| format!("`{}`", escape_template_name_string(x.name()))) + .unwrap_or_else(|| format!("${}", current_instance.unwrap())); + let body = to_js_value_array(&i.args, arg_to_js_value)?; + + Ok(Box::new(JSNode::Invoke { + instance: instanceish, + name: escape_template_name_string(i.name), + body: body, + })) +} + +fn execute_to_js( + current_instance: &Option<usize>, + exec: wast::WastExecute, + wast: &str, +) -> Result<Box<JSNode>> { + match exec { + wast::WastExecute::Invoke(invoke) => invoke_to_js(current_instance, invoke), + wast::WastExecute::Wat(module) => { + let text = match module { + wast::Wat::Module(module) => module_to_js_string(&module, wast)?, + other => bail!("unsupported {:?} at execute_to_js", other), + }; + Ok(Box::new(JSNode::Raw(format!("instantiate(`{}`)", text)))) + } + wast::WastExecute::Get { module, global } => { + let instanceish = module + .map(|x| format!("`{}`", escape_template_name_string(x.name()))) + .unwrap_or_else(|| format!("${}", current_instance.unwrap())); + Ok(Box::new(JSNode::Raw(format!( + "get({}, `{}`)", + instanceish, global + )))) + } + } +} + +fn to_js_value_array<T, F: Fn(&T) -> Result<String>>(vals: &[T], func: F) -> Result<Box<JSNode>> { + let mut value_nodes: Vec<Box<JSNode>> = vec![]; + for val in vals { + let converted_value = (func)(val)?; + value_nodes.push(Box::new(JSNode::Raw(converted_value))); + } + + Ok(Box::new(JSNode::Array(value_nodes))) +} + +fn to_js_value_array_string<T, F: Fn(&T) -> Result<String>>(vals: &[T], func: F) -> Result<String> { + let array = to_js_value_array(vals, func)?; + Ok(array.output(NOWRAP)) +} + +fn f32_needs_bits(a: f32) -> bool { + if a.is_infinite() { + return false; + } + return a.is_nan() + || ((a as f64) as f32).to_bits() != a.to_bits() + || (format!("{:.}", a).parse::<f64>().unwrap() as f32).to_bits() != a.to_bits(); +} + +fn f64_needs_bits(a: f64) -> bool { + return a.is_nan(); +} + +fn f32x4_needs_bits(a: &[wast::token::Float32; 4]) -> bool { + a.iter().any(|x| { + let as_f32 = f32::from_bits(x.bits); + f32_needs_bits(as_f32) + }) +} + +fn f64x2_needs_bits(a: &[wast::token::Float64; 2]) -> bool { + a.iter().any(|x| { + let as_f64 = f64::from_bits(x.bits); + f64_needs_bits(as_f64) + }) +} + +fn f32_to_js_value(val: f32) -> String { + if val == f32::INFINITY { + format!("Infinity") + } else if val == f32::NEG_INFINITY { + format!("-Infinity") + } else if val.is_sign_negative() && val == 0f32 { + format!("-0") + } else { + format!("{:.}", val) + } +} + +fn f64_to_js_value(val: f64) -> String { + if val == f64::INFINITY { + format!("Infinity") + } else if val == f64::NEG_INFINITY { + format!("-Infinity") + } else if val.is_sign_negative() && val == 0f64 { + format!("-0") + } else { + format!("{:.}", val) + } +} + +fn float32_to_js_value(val: &wast::token::Float32) -> String { + let as_f32 = f32::from_bits(val.bits); + if f32_needs_bits(as_f32) { + format!( + "bytes(\"f32\", {})", + to_js_value_array_string(&val.bits.to_le_bytes(), |x| Ok(format!("0x{:x}", x))) + .unwrap(), + ) + } else { + format!("value(\"f32\", {})", f32_to_js_value(as_f32)) + } +} + +fn float64_to_js_value(val: &wast::token::Float64) -> String { + let as_f64 = f64::from_bits(val.bits); + if f64_needs_bits(as_f64) { + format!( + "bytes(\"f64\", {})", + to_js_value_array_string(&val.bits.to_le_bytes(), |x| Ok(format!("0x{:x}", x))) + .unwrap(), + ) + } else { + format!("value(\"f64\", {})", f64_to_js_value(as_f64)) + } +} + +fn f32_pattern_to_js_value(pattern: &wast::core::NanPattern<wast::token::Float32>) -> String { + use wast::core::NanPattern::*; + match pattern { + CanonicalNan => format!("`canonical_nan`"), + ArithmeticNan => format!("`arithmetic_nan`"), + Value(x) => float32_to_js_value(x), + } +} + +fn f64_pattern_to_js_value(pattern: &wast::core::NanPattern<wast::token::Float64>) -> String { + use wast::core::NanPattern::*; + match pattern { + CanonicalNan => format!("`canonical_nan`"), + ArithmeticNan => format!("`arithmetic_nan`"), + Value(x) => float64_to_js_value(x), + } +} + +fn return_value_to_js_value(v: &wast::core::WastRetCore<'_>) -> Result<String> { + use wast::core::WastRetCore::*; + Ok(match v { + I32(x) => format!("value(\"i32\", {})", x), + I64(x) => format!("value(\"i64\", {}n)", x), + F32(x) => f32_pattern_to_js_value(x), + F64(x) => f64_pattern_to_js_value(x), + RefNull(x) => match x { + Some(wast::core::HeapType::Any) => format!("value('anyref', null)"), + Some(wast::core::HeapType::Exn) => format!("value('exnref', null)"), + Some(wast::core::HeapType::Eq) => format!("value('eqref', null)"), + Some(wast::core::HeapType::Array) => format!("value('arrayref', null)"), + Some(wast::core::HeapType::Struct) => format!("value('structref', null)"), + Some(wast::core::HeapType::I31) => format!("value('i31ref', null)"), + Some(wast::core::HeapType::None) => format!("value('nullref', null)"), + Some(wast::core::HeapType::Func) => format!("value('anyfunc', null)"), + Some(wast::core::HeapType::NoFunc) => format!("value('nullfuncref', null)"), + Some(wast::core::HeapType::Extern) => format!("value('externref', null)"), + Some(wast::core::HeapType::NoExtern) => format!("value('nullexternref', null)"), + None => "null".to_string(), + other => bail!( + "couldn't convert ref.null {:?} to a js assertion value", + other + ), + }, + RefFunc(_) => format!("new RefWithType('funcref')"), + RefExtern(x) => match x { + Some(v) => format!("new ExternRefResult({})", v), + None => format!("new RefWithType('externref')"), + }, + RefHost(x) => format!("new HostRefResult({})", x), + RefAny => format!("new RefWithType('anyref')"), + RefEq => format!("new RefWithType('eqref')"), + RefArray => format!("new RefWithType('arrayref')"), + RefStruct => format!("new RefWithType('structref')"), + RefI31 => format!("new RefWithType('i31ref')"), + V128(x) => { + use wast::core::V128Pattern::*; + match x { + I8x16(elements) => format!( + "i8x16({})", + to_js_value_array_string(elements, |x| Ok(format!("0x{:x}", x)))?, + ), + I16x8(elements) => format!( + "i16x8({})", + to_js_value_array_string(elements, |x| Ok(format!("0x{:x}", x)))?, + ), + I32x4(elements) => format!( + "i32x4({})", + to_js_value_array_string(elements, |x| Ok(format!("0x{:x}", x)))?, + ), + I64x2(elements) => format!( + "i64x2({})", + to_js_value_array_string(elements, |x| Ok(format!("0x{:x}n", x)))?, + ), + F32x4(elements) => { + let elements: Vec<String> = elements + .iter() + .map(|x| f32_pattern_to_js_value(x)) + .collect(); + let elements = strings_to_raws(elements); + JSNode::Call { + name: "new F32x4Pattern".to_string(), + args: elements, + } + .output(0) + } + F64x2(elements) => { + let elements: Vec<String> = elements + .iter() + .map(|x| f64_pattern_to_js_value(x)) + .collect(); + let elements = strings_to_raws(elements); + JSNode::Call { + name: "new F64x2Pattern".to_string(), + args: elements, + } + .output(0) + } + } + } + Either(v) => { + let args = v + .iter() + .map(|v| return_value_to_js_value(v).unwrap()) + .collect(); + let args = strings_to_raws(args); + JSNode::Call { + name: "either".to_string(), + args: args, + } + .output(0) + } + other => bail!("couldn't convert Core({:?}) to a js assertion value", other), + }) +} + +fn assert_expression_to_js_value(v: &wast::WastRet<'_>) -> Result<String> { + use wast::WastRet::*; + + Ok(match &v { + Core(x) => return_value_to_js_value(x)?, + other => bail!("couldn't convert {:?} to a js assertion value", other), + }) +} + +fn arg_to_js_value(v: &wast::WastArg<'_>) -> Result<String> { + use wast::core::WastArgCore::*; + use wast::WastArg::Core; + + Ok(match &v { + Core(I32(x)) => format!("{}", *x), + Core(I64(x)) => format!("{}n", *x), + Core(F32(x)) => float32_to_js_value(x), + Core(F64(x)) => float64_to_js_value(x), + Core(V128(x)) => { + use wast::core::V128Const::*; + match x { + I8x16(elements) => format!( + "i8x16({})", + to_js_value_array_string(elements, |x| Ok(format!("0x{:x}", x)))?, + ), + I16x8(elements) => format!( + "i16x8({})", + to_js_value_array_string(elements, |x| Ok(format!("0x{:x}", x)))?, + ), + I32x4(elements) => format!( + "i32x4({})", + to_js_value_array_string(elements, |x| Ok(format!("0x{:x}", x)))?, + ), + I64x2(elements) => format!( + "i64x2({})", + to_js_value_array_string(elements, |x| Ok(format!("0x{:x}n", x)))?, + ), + F32x4(elements) => { + if f32x4_needs_bits(elements) { + let bytes = + to_js_value_array(&x.to_le_bytes(), |x| Ok(format!("0x{:x}", x)))?; + format!("bytes('v128', {})", bytes.output(0)) + } else { + let vals = to_js_value_array(elements, |x| { + Ok(f32_to_js_value(f32::from_bits(x.bits))) + })?; + format!("f32x4({})", vals.output(0)) + } + } + F64x2(elements) => { + if f64x2_needs_bits(elements) { + let bytes = + to_js_value_array(&x.to_le_bytes(), |x| Ok(format!("0x{:x}", x)))?; + format!("bytes('v128', {})", bytes.output(0)) + } else { + let vals = to_js_value_array(elements, |x| { + Ok(f64_to_js_value(f64::from_bits(x.bits))) + })?; + format!("f64x2({})", vals.output(0)) + } + } + } + } + Core(RefNull(_)) => format!("null"), + Core(RefExtern(x)) => format!("externref({})", x), + Core(RefHost(x)) => format!("hostref({})", x), + other => bail!("couldn't convert {:?} to a js value", other), + }) +} diff --git a/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/harness.js b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/harness.js new file mode 100644 index 0000000000..a96781e8ed --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/harness.js @@ -0,0 +1,448 @@ +"use strict"; + +/* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +if (!wasmIsSupported()) { + quit(); +} + +function bytes(type, bytes) { + var typedBuffer = new Uint8Array(bytes); + return wasmGlobalFromArrayBuffer(type, typedBuffer.buffer); +} +function value(type, value) { + return new WebAssembly.Global({ + value: type, + mutable: false, + }, value); +} + +function i8x16(elements) { + let typedBuffer = new Uint8Array(elements); + return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer); +} +function i16x8(elements) { + let typedBuffer = new Uint16Array(elements); + return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer); +} +function i32x4(elements) { + let typedBuffer = new Uint32Array(elements); + return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer); +} +function i64x2(elements) { + let typedBuffer = new BigUint64Array(elements); + return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer); +} +function f32x4(elements) { + let typedBuffer = new Float32Array(elements); + return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer); +} +function f64x2(elements) { + let typedBuffer = new Float64Array(elements); + return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer); +} + +function either(...arr) { + return new EitherVariants(arr); +} + +class F32x4Pattern { + constructor(x, y, z, w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } +} + +class F64x2Pattern { + constructor(x, y) { + this.x = x; + this.y = y; + } +} + +class RefWithType { + constructor(type) { + this.type = type; + } + + formatExpected() { + return `RefWithType(${this.type})`; + } + + test(refGlobal) { + try { + new WebAssembly.Global({value: this.type}, refGlobal.value); + return true; + } catch (err) { + assertEq(err instanceof TypeError, true, `wrong type of error when creating global: ${err}`); + assertEq(!!err.message.match(/can only pass/), true, `wrong type of error when creating global: ${err}`); + return false; + } + } +} + +// ref.extern values created by spec tests will be JS objects of the form +// { [externsym]: <number> }. Other externref values are possible to observe +// if extern.convert_any is used. +let externsym = Symbol("externref"); +function externref(s) { + return { [externsym]: s }; +} +function is_externref(x) { + return (x !== null && externsym in x) ? 1 : 0; +} +function is_funcref(x) { + return typeof x === "function" ? 1 : 0; +} +function eq_externref(x, y) { + return x === y ? 1 : 0; +} +function eq_funcref(x, y) { + return x === y ? 1 : 0; +} + +class ExternRefResult { + constructor(n) { + this.n = n; + } + + formatExpected() { + return `ref.extern ${this.n}`; + } + + test(global) { + // the global's value can either be an externref or just a plain old JS number + let result = global.value; + if (typeof global.value === "object" && externsym in global.value) { + result = global.value[externsym]; + } + return result === this.n; + } +} + +// ref.host values created by spectests will be whatever the JS API does to +// convert the given value to anyref. It should implicitly be like any.convert_extern. +function hostref(v) { + if (!wasmGcEnabled()) { + throw new Error("ref.host only works when wasm GC is enabled"); + } + + const { internalizeNum } = new WebAssembly.Instance( + new WebAssembly.Module(wasmTextToBinary(`(module + (func (import "test" "coerce") (param i32) (result anyref)) + (func (export "internalizeNum") (param i32) (result anyref) + (call 0 (local.get 0)) + ) + )`)), + { "test": { "coerce": x => x } }, + ).exports; + return internalizeNum(v); +} + +class HostRefResult { + constructor(n) { + this.n = n; + } + + formatExpected() { + return `ref.host ${this.n}`; + } + + test(externrefGlobal) { + assertEq(externsym in externrefGlobal.value, true, `HostRefResult only works with externref inputs`); + return externrefGlobal.value[externsym] === this.n; + } +} + +let spectest = { + externref: externref, + is_externref: is_externref, + is_funcref: is_funcref, + eq_externref: eq_externref, + eq_funcref: eq_funcref, + print: console.log.bind(console), + print_i32: console.log.bind(console), + print_i32_f32: console.log.bind(console), + print_f64_f64: console.log.bind(console), + print_f32: console.log.bind(console), + print_f64: console.log.bind(console), + global_i32: 666, + global_i64: 666n, + global_f32: 666, + global_f64: 666, + table: new WebAssembly.Table({ + initial: 10, + maximum: 20, + element: "anyfunc", + }), + memory: new WebAssembly.Memory({ initial: 1, maximum: 2 }), +}; + +let linkage = { + spectest, +}; + +function getInstance(instanceish) { + if (typeof instanceish === "string") { + assertEq( + instanceish in linkage, + true, + `'${instanceish}'' must be registered`, + ); + return linkage[instanceish]; + } + return instanceish; +} + +function instantiate(source) { + let bytecode = wasmTextToBinary(source); + let module = new WebAssembly.Module(bytecode); + let instance = new WebAssembly.Instance(module, linkage); + return instance.exports; +} + +function register(instanceish, name) { + linkage[name] = getInstance(instanceish); +} + +function invoke(instanceish, field, params) { + let func = getInstance(instanceish)[field]; + assertEq(func instanceof Function, true, "expected a function"); + return wasmLosslessInvoke(func, ...params); +} + +function get(instanceish, field) { + let global = getInstance(instanceish)[field]; + assertEq( + global instanceof WebAssembly.Global, + true, + "expected a WebAssembly.Global", + ); + return global; +} + +function assert_trap(thunk, message) { + try { + thunk(); + throw new Error("expected trap"); + } catch (err) { + if (err instanceof WebAssembly.RuntimeError) { + return; + } + throw err; + } +} + +let StackOverflow; +try { + (function f() { + 1 + f(); + })(); +} catch (e) { + StackOverflow = e.constructor; +} +function assert_exhaustion(thunk, message) { + try { + thunk(); + assertEq("normal return", "exhaustion"); + } catch (err) { + assertEq( + err instanceof StackOverflow, + true, + "expected exhaustion", + ); + } +} + +function assert_invalid(thunk, message) { + try { + thunk(); + assertEq("valid module", "invalid module"); + } catch (err) { + assertEq( + err instanceof WebAssembly.LinkError || + err instanceof WebAssembly.CompileError, + true, + "expected an invalid module", + ); + } +} + +function assert_unlinkable(thunk, message) { + try { + thunk(); + assertEq(true, false, "expected an unlinkable module"); + } catch (err) { + assertEq( + err instanceof WebAssembly.LinkError || + err instanceof WebAssembly.CompileError, + true, + "expected an unlinkable module", + ); + } +} + +function assert_malformed(thunk, message) { + try { + thunk(); + assertEq("valid module", "malformed module"); + } catch (err) { + assertEq( + err instanceof TypeError || + err instanceof SyntaxError || + err instanceof WebAssembly.CompileError || + err instanceof WebAssembly.LinkError, + true, + `expected a malformed module`, + ); + } +} + +function assert_exception(thunk) { + let thrown = false; + try { + thunk(); + } catch (err) { + thrown = true; + } + assertEq(thrown, true, "expected an exception to be thrown"); +} + +function assert_return(thunk, expected) { + let results = thunk(); + + if (results === undefined) { + results = []; + } else if (!Array.isArray(results)) { + results = [results]; + } + if (!Array.isArray(expected)) { + expected = [expected]; + } + + if (!compareResults(results, expected)) { + let got = results.map((x) => formatResult(x)).join(", "); + let wanted = expected.map((x) => formatExpected(x)).join(", "); + assertEq( + `[${got}]`, + `[${wanted}]`, + ); + assertEq(true, false, `${got} !== ${wanted}`); + } +} + +function formatResult(result) { + if (typeof (result) === "object") { + return wasmGlobalToString(result); + } else { + return `${result}`; + } +} + +function formatExpected(expected) { + if ( + expected === `f32_canonical_nan` || + expected === `f32_arithmetic_nan` || + expected === `f64_canonical_nan` || + expected === `f64_arithmetic_nan` + ) { + return expected; + } else if (expected instanceof F32x4Pattern) { + return `f32x4(${formatExpected(expected.x)}, ${ + formatExpected(expected.y) + }, ${formatExpected(expected.z)}, ${formatExpected(expected.w)})`; + } else if (expected instanceof F64x2Pattern) { + return `f64x2(${formatExpected(expected.x)}, ${ + formatExpected(expected.y) + })`; + } else if (expected instanceof EitherVariants) { + return expected.formatExpected(); + } else if (expected instanceof RefWithType) { + return expected.formatExpected(); + } else if (expected instanceof ExternRefResult) { + return expected.formatExpected(); + } else if (expected instanceof HostRefResult) { + return expected.formatExpected(); + } else if (typeof (expected) === "object") { + return wasmGlobalToString(expected); + } else { + throw new Error("unknown expected result"); + } +} + +class EitherVariants { + constructor(arr) { + this.arr = arr; + } + matches(v) { + return this.arr.some((e) => compareResult(v, e)); + } + formatExpected() { + return `either(${this.arr.map(formatExpected).join(", ")})`; + } +} + +function compareResults(results, expected) { + if (results.length !== expected.length) { + return false; + } + for (let i in results) { + if (expected[i] instanceof EitherVariants) { + return expected[i].matches(results[i]); + } + if (!compareResult(results[i], expected[i])) { + return false; + } + } + return true; +} + +function compareResult(result, expected) { + if ( + expected === `canonical_nan` || + expected === `arithmetic_nan` + ) { + return wasmGlobalIsNaN(result, expected); + } else if (expected === null) { + return result.value === null; + } else if (expected instanceof F32x4Pattern) { + return compareResult( + wasmGlobalExtractLane(result, "f32x4", 0), + expected.x, + ) && + compareResult(wasmGlobalExtractLane(result, "f32x4", 1), expected.y) && + compareResult(wasmGlobalExtractLane(result, "f32x4", 2), expected.z) && + compareResult(wasmGlobalExtractLane(result, "f32x4", 3), expected.w); + } else if (expected instanceof F64x2Pattern) { + return compareResult( + wasmGlobalExtractLane(result, "f64x2", 0), + expected.x, + ) && + compareResult(wasmGlobalExtractLane(result, "f64x2", 1), expected.y); + } else if (expected instanceof RefWithType) { + return expected.test(result); + } else if (expected instanceof ExternRefResult) { + return expected.test(result); + } else if (expected instanceof HostRefResult) { + return expected.test(result); + } else if (typeof (expected) === "object") { + return wasmGlobalsEqual(result, expected); + } else { + throw new Error("unknown expected result"); + } +} diff --git a/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/lib.rs b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/lib.rs new file mode 100644 index 0000000000..b62bcbfaea --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/lib.rs @@ -0,0 +1,18 @@ +/* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +mod convert; +mod out; +pub use convert::*; diff --git a/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/license.js b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/license.js new file mode 100644 index 0000000000..12d51bd702 --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/license.js @@ -0,0 +1,14 @@ +/* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ diff --git a/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/main.rs b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/main.rs new file mode 100644 index 0000000000..d5d1118e7e --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/main.rs @@ -0,0 +1,40 @@ +/* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use anyhow::{Context as _, Result}; +use std::env; +use std::path::Path; + +mod convert; +mod out; + +fn main() -> Result<()> { + let files = env::args().collect::<Vec<String>>(); + for path in &files[1..] { + let source = + std::fs::read_to_string(path).with_context(|| format!("failed to read `{}`", path))?; + + let mut full_script = String::new(); + full_script.push_str(&convert::harness()); + full_script.push_str(&convert::convert(path, &source)?); + + std::fs::write( + Path::new(path).with_extension("js").file_name().unwrap(), + &full_script, + )?; + } + + Ok(()) +} diff --git a/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/out.rs b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/out.rs new file mode 100644 index 0000000000..fef1b82ade --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/out.rs @@ -0,0 +1,188 @@ +/* Copyright 2022 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/// This module has a bare-bones "syntax tree" that helps us output more readable JS code in our +/// tests. This is a lot cheaper and faster than running a full JS formatter on the generated code. +/// The tree only has node types for things that matter to formatting; most things in the output +/// use the Raw type, which is just a plain old string. + +pub const LINE_WIDTH: usize = 80; // the default line width to try to meet (will not be met perfectly) +pub const NOWRAP: usize = 1000; // use for line_remaining when you don't want to wrap text + +pub enum JSNode { + Assert { + name: String, + exec: Box<JSNode>, + expected: Box<JSNode>, + }, + Invoke { + instance: String, + name: String, + body: Box<JSNode>, + }, + Call { + name: String, + args: Vec<Box<JSNode>>, + }, + Array(Vec<Box<JSNode>>), + Raw(String), +} + +impl JSNode { + /// Converts a node to JavaScript code. line_remaining is the number of characters remaining on + /// the line, used for line-wrapping purposes; if zero, the default LINE_WIDTH will be used. + pub fn output(&self, line_remaining: usize) -> String { + let line_remaining = if line_remaining == 0 { + LINE_WIDTH + } else { + line_remaining + }; + match self { + Self::Assert { + name, + exec, + expected, + } => { + if self.len() > line_remaining { + format!( + "{}(\n{}\n)", + name, + indent(format!( + "() => {},\n{},", + exec.output(line_remaining - 8), + expected.output(line_remaining - 2), + )), + ) + } else { + format!( + "{}(() => {}, {})", + name, + exec.output(NOWRAP), + expected.output(NOWRAP), + ) + } + } + Self::Invoke { + instance, + name, + body, + } => { + let len_before_body = "invoke".len() + instance.len() + name.len(); + if len_before_body > line_remaining { + format!( + "invoke({},\n{}\n)", + instance, + indent(format!("`{}`,\n{},", name, body.output(line_remaining - 2),)), + ) + } else { + let body_string = body.output(line_remaining - len_before_body); + format!("invoke({}, `{}`, {})", instance, name, body_string) + } + } + Self::Call { name, args } => { + if self.len() > line_remaining { + let arg_strings: Vec<String> = args + .iter() + .map(|arg| arg.output(line_remaining - 2)) + .collect(); + format!("{}(\n{},\n)", name, indent(arg_strings.join(",\n"))) + } else { + let arg_strings: Vec<String> = + args.iter().map(|arg| arg.output(NOWRAP)).collect(); + format!("{}({})", name, arg_strings.join(", ")) + } + } + Self::Array(values) => { + if self.len() > line_remaining { + let value_strings = output_nodes(&values, 70); + format!("[\n{},\n]", indent(value_strings.join(",\n"))) + } else { + let value_strings = output_nodes(&values, 80); + format!("[{}]", value_strings.join(", ")) + } + } + Self::Raw(val) => val.to_string(), + } + } + + /// A rough estimate of the string length of the node. Used as a heuristic to know when we + /// should wrap text. + fn len(&self) -> usize { + match self { + Self::Assert { + name, + exec, + expected, + } => name.len() + exec.len() + expected.len(), + Self::Invoke { + instance, + name, + body, + } => instance.len() + name.len() + body.len(), + Self::Call { name, args } => { + let mut args_len: usize = 0; + for node in args { + args_len += node.len() + ", ".len(); + } + name.len() + args_len + } + Self::Array(nodes) => { + let mut content_len: usize = 0; + for node in nodes { + content_len += node.len() + ", ".len(); + } + content_len + } + Self::Raw(s) => s.len(), + } + } +} + +pub fn output_nodes(nodes: &Vec<Box<JSNode>>, line_width_per_node: usize) -> Vec<String> { + nodes + .iter() + .map(|node| node.output(line_width_per_node)) + .collect() +} + +pub fn strings_to_raws(strs: Vec<String>) -> Vec<Box<JSNode>> { + let mut res: Vec<Box<JSNode>> = vec![]; + for s in strs { + res.push(Box::new(JSNode::Raw(s))); + } + res +} + +fn indent(s: String) -> String { + let mut result = String::new(); + let mut do_indent = true; + for (i, line) in s.lines().enumerate() { + if i > 0 { + result.push('\n'); + } + if line.chars().any(|c| !c.is_whitespace()) { + if do_indent { + result.push_str(" "); + } + result.push_str(line); + + // An odd number of backticks in the line means we are entering or exiting a raw string. + if line.matches("`").count() % 2 == 1 { + do_indent = !do_indent + } + } + } + result +} diff --git a/js/src/jit-test/etc/wasm/spec-tests.patch b/js/src/jit-test/etc/wasm/spec-tests.patch new file mode 100644 index 0000000000..a3c0e15676 --- /dev/null +++ b/js/src/jit-test/etc/wasm/spec-tests.patch @@ -0,0 +1,647 @@ +# HG changeset patch +# User Ryan Hunt <rhunt@eqrion.net> +# Date 1685551062 18000 +# Wed May 31 11:37:42 2023 -0500 +# Node ID 551e7bae9a6bb2634680b8129af7634ceeccc648 +# Parent 1b28ba88d59584fcf977974a625a179b5bdbdabf +Spec test patches rollup. + +1. Bug 1737225 - Disable some tests on arm. r=yury + +Disable a partial-oob test on arm/arm64 because some hardware will +perform byte-at-a-time writes at the end of the heap, and we have +not fixed that yet. + +diff --git a/js/src/jit-test/tests/wasm/spec/memory64/align64.wast.js b/js/src/jit-test/tests/wasm/spec/memory64/align64.wast.js +--- a/js/src/jit-test/tests/wasm/spec/memory64/align64.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/memory64/align64.wast.js +@@ -1076,8 +1076,15 @@ let $24 = instantiate(`(module + ) + )`); + +-// ./test/core/align64.wast:864 +-assert_trap(() => invoke($24, `store`, [65532n, -1n]), `out of bounds memory access`); ++// Bug 1737225 - do not observe the partial store caused by bug 1666747 on ++// some native platforms. ++if (!partialOobWriteMayWritePartialData()) { ++ // ./test/core/align64.wast:864 ++ assert_trap( ++ () => invoke($24, `store`, [65532n, -1n]), ++ `out of bounds memory access`, ++ ); + +-// ./test/core/align64.wast:866 +-assert_return(() => invoke($24, `load`, [65532n]), [value("i32", 0)]); ++ // ./test/core/align64.wast:866 ++ assert_return(() => invoke($24, `load`, [65532n]), [value("i32", 0)]); ++} +diff --git a/js/src/jit-test/tests/wasm/spec/memory64/harness/harness.js b/js/src/jit-test/tests/wasm/spec/memory64/harness/harness.js +--- a/js/src/jit-test/tests/wasm/spec/memory64/harness/harness.js ++++ b/js/src/jit-test/tests/wasm/spec/memory64/harness/harness.js +@@ -19,6 +19,15 @@ if (!wasmIsSupported()) { + quit(); + } + ++function partialOobWriteMayWritePartialData() { ++ let arm_native = getBuildConfiguration("arm") && !getBuildConfiguration("arm-simulator"); ++ let arm64_native = getBuildConfiguration("arm64") && !getBuildConfiguration("arm64-simulator"); ++ return arm_native || arm64_native; ++} ++ ++let native_arm = getBuildConfiguration("arm") && !getBuildConfiguration("arm-simulator"); ++let native_arm64 = getBuildConfiguration("arm64") && !getBuildConfiguration("arm64-simulator"); ++ + function bytes(type, bytes) { + var typedBuffer = new Uint8Array(bytes); + return wasmGlobalFromArrayBuffer(type, typedBuffer.buffer); +diff --git a/js/src/jit-test/tests/wasm/spec/memory64/memory_trap64.wast.js b/js/src/jit-test/tests/wasm/spec/memory64/memory_trap64.wast.js +--- a/js/src/jit-test/tests/wasm/spec/memory64/memory_trap64.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/memory64/memory_trap64.wast.js +@@ -617,8 +617,16 @@ assert_trap(() => invoke($1, `i64.load32 + // ./test/core/memory_trap64.wast:265 + assert_trap(() => invoke($1, `i64.load32_u`, [-4n]), `out of bounds memory access`); + +-// ./test/core/memory_trap64.wast:268 +-assert_return(() => invoke($1, `i64.load`, [65528n]), [value("i64", 7523094288207667809n)]); ++// Bug 1737225 - do not observe the partial store caused by bug 1666747 on ++// some native platforms. ++if (!partialOobWriteMayWritePartialData()) { ++ // ./test/core/memory_trap64.wast:268 ++ assert_return(() => invoke($1, `i64.load`, [65528n]), [ ++ value("i64", 7523094288207667809n), ++ ]); + +-// ./test/core/memory_trap64.wast:269 +-assert_return(() => invoke($1, `i64.load`, [0n]), [value("i64", 7523094288207667809n)]); ++ // ./test/core/memory_trap64.wast:269 ++ assert_return(() => invoke($1, `i64.load`, [0n]), [ ++ value("i64", 7523094288207667809n), ++ ]); ++} +diff --git a/js/src/jit-test/tests/wasm/spec/multi-memory/simd_load.wast.js b/js/src/jit-test/tests/wasm/spec/multi-memory/simd_load.wast.js +--- a/js/src/jit-test/tests/wasm/spec/multi-memory/simd_load.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/multi-memory/simd_load.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/multi-memory/simd_memory-multi.wast.js b/js/src/jit-test/tests/wasm/spec/multi-memory/simd_memory-multi.wast.js +--- a/js/src/jit-test/tests/wasm/spec/multi-memory/simd_memory-multi.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/multi-memory/simd_memory-multi.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/multi-memory/simd_store.wast.js b/js/src/jit-test/tests/wasm/spec/multi-memory/simd_store.wast.js +--- a/js/src/jit-test/tests/wasm/spec/multi-memory/simd_store.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/multi-memory/simd_store.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/relaxed-simd/i32x4_relaxed_trunc.wast.js b/js/src/jit-test/tests/wasm/spec/relaxed-simd/i32x4_relaxed_trunc.wast.js +--- a/js/src/jit-test/tests/wasm/spec/relaxed-simd/i32x4_relaxed_trunc.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/relaxed-simd/i32x4_relaxed_trunc.wast.js +@@ -92,6 +92,7 @@ assert_return( + either( + i32x4([0x0, 0x0, 0xffffff00, 0xffffffff]), + i32x4([0x0, 0xffffffff, 0xffffff00, 0xffffffff]), ++ i32x4([0x0, 0xffffffff, 0xffffff00, 0x0]), + ), + ], + ); +@@ -122,6 +123,7 @@ assert_return( + either( + i32x4([0x0, 0x0, 0x0, 0x0]), + i32x4([0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff]), ++ i32x4([0x80000000, 0x80000000, 0x80000000, 0x80000000]), + ), + ], + ); +@@ -173,6 +175,7 @@ assert_return( + either( + i32x4([0x0, 0xffffffff, 0x0, 0x0]), + i32x4([0xffffffff, 0xffffffff, 0x0, 0x0]), ++ i32x4([0xfffffffe, 0x0, 0x0, 0x0]), + ), + ], + ); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_address.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_address.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_address.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_address.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_bit_shift.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_bit_shift.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_bit_shift.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_bit_shift.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_bitwise.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_bitwise.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_bitwise.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_bitwise.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_boolean.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_boolean.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_boolean.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_boolean.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_const.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_const.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_const.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_const.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_conversions.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_conversions.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_conversions.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_conversions.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_f32x4.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_f32x4.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_f32x4.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_f32x4.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_f32x4_arith.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_f32x4_arith.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_f32x4_arith.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_f32x4_arith.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_f32x4_cmp.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_f32x4_cmp.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_f32x4_cmp.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_f32x4_cmp.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_f32x4_pmin_pmax.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_f32x4_pmin_pmax.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_f32x4_pmin_pmax.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_f32x4_pmin_pmax.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_f32x4_rounding.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_f32x4_rounding.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_f32x4_rounding.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_f32x4_rounding.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_f64x2.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_f64x2.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_f64x2.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_f64x2.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_f64x2_arith.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_f64x2_arith.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_f64x2_arith.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_f64x2_arith.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_f64x2_cmp.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_f64x2_cmp.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_f64x2_cmp.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_f64x2_cmp.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_f64x2_pmin_pmax.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_f64x2_pmin_pmax.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_f64x2_pmin_pmax.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_f64x2_pmin_pmax.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_f64x2_rounding.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_f64x2_rounding.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_f64x2_rounding.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_f64x2_rounding.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_arith.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_arith.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_arith.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_arith.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_arith2.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_arith2.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_arith2.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_arith2.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_cmp.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_cmp.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_cmp.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_cmp.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_extadd_pairwise_i8x16.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_extadd_pairwise_i8x16.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_extadd_pairwise_i8x16.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_extadd_pairwise_i8x16.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_extmul_i8x16.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_extmul_i8x16.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_extmul_i8x16.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_extmul_i8x16.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_q15mulr_sat_s.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_q15mulr_sat_s.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_q15mulr_sat_s.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_q15mulr_sat_s.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_sat_arith.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_sat_arith.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_sat_arith.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i16x8_sat_arith.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_arith.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_arith.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_arith.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_arith.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_arith2.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_arith2.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_arith2.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_arith2.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_cmp.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_cmp.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_cmp.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_cmp.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_dot_i16x8.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_dot_i16x8.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_dot_i16x8.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_dot_i16x8.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_extadd_pairwise_i16x8.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_extadd_pairwise_i16x8.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_extadd_pairwise_i16x8.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_extadd_pairwise_i16x8.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_extmul_i16x8.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_extmul_i16x8.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_extmul_i16x8.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_extmul_i16x8.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_trunc_sat_f32x4.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_trunc_sat_f32x4.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_trunc_sat_f32x4.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_trunc_sat_f32x4.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_trunc_sat_f64x2.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_trunc_sat_f64x2.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_trunc_sat_f64x2.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i32x4_trunc_sat_f64x2.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i64x2_arith.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i64x2_arith.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i64x2_arith.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i64x2_arith.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i64x2_arith2.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i64x2_arith2.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i64x2_arith2.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i64x2_arith2.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i64x2_cmp.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i64x2_cmp.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i64x2_cmp.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i64x2_cmp.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i64x2_extmul_i32x4.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i64x2_extmul_i32x4.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i64x2_extmul_i32x4.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i64x2_extmul_i32x4.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i8x16_arith.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i8x16_arith.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i8x16_arith.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i8x16_arith.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i8x16_arith2.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i8x16_arith2.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i8x16_arith2.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i8x16_arith2.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i8x16_cmp.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i8x16_cmp.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i8x16_cmp.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i8x16_cmp.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_i8x16_sat_arith.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_i8x16_sat_arith.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_i8x16_sat_arith.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_i8x16_sat_arith.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_int_to_int_extend.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_int_to_int_extend.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_int_to_int_extend.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_int_to_int_extend.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_lane.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_lane.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_lane.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_lane.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_load.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_load.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_load.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_load.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_load16_lane.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_load16_lane.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_load16_lane.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_load16_lane.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_load32_lane.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_load32_lane.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_load32_lane.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_load32_lane.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_load64_lane.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_load64_lane.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_load64_lane.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_load64_lane.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_load8_lane.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_load8_lane.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_load8_lane.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_load8_lane.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_load_extend.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_load_extend.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_load_extend.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_load_extend.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_load_splat.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_load_splat.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_load_splat.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_load_splat.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_load_zero.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_load_zero.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_load_zero.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_load_zero.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_splat.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_splat.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_splat.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_splat.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_store.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_store.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_store.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_store.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_store16_lane.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_store16_lane.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_store16_lane.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_store16_lane.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_store32_lane.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_store32_lane.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_store32_lane.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_store32_lane.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_store64_lane.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_store64_lane.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_store64_lane.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_store64_lane.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/simd_store8_lane.wast.js b/js/src/jit-test/tests/wasm/spec/spec/simd_store8_lane.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/simd_store8_lane.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/simd_store8_lane.wast.js +@@ -1,3 +1,5 @@ ++// |jit-test| skip-if: !wasmSimdEnabled() ++ + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/function-references/return_call_ref.wast.js b/js/src/jit-test/tests/wasm/spec/function-references/return_call_ref.wast.js +index 3ea51a8cb0ff3..71739f4a1c8e4 100644 +--- a/js/src/jit-test/tests/wasm/spec/function-references/return_call_ref.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/function-references/return_call_ref.wast.js +@@ -1,3 +1,4 @@ ++// |jit-test| --wasm-tail-calls; skip-if: !wasmTailCallsEnabled() + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); +diff --git a/js/src/jit-test/tests/wasm/spec/spec/global.wast.js b/js/src/jit-test/tests/wasm/spec/spec/global.wast.js +--- a/js/src/jit-test/tests/wasm/spec/spec/global.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/spec/global.wast.js +@@ -1,3 +1,4 @@ ++// |jit-test| --no-wasm-gc + /* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + |