diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /js/src/jit-test | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jit-test')
77 files changed, 8581 insertions, 0 deletions
diff --git a/js/src/jit-test/README b/js/src/jit-test/README new file mode 100644 index 0000000000..b8b01af4b5 --- /dev/null +++ b/js/src/jit-test/README @@ -0,0 +1,107 @@ +JS Internal Test Suite + +* PURPOSE + +This is a test suite primarily for testing the SpiderMonkey JIT, GC, and any +other internal mechanisms that are not visible to test262. All tests are run in +the JS shell. + +In the future, we intend to migrate the "non262" jstests over to this framework. + +* CONTINUOUS INTEGRATION + +In CI, these tests will be run as part of the "SM(...)" set of jobs. They will +also be packaged up and then run via mozharness as separate test jobs on some +platforms. These will appear on treeherder as jobs starting with "Jit", eg +"Jit", "Jit3", "Jit-1proc3", etc. + +Unlike jstests, we do not run jit-tests in the browser. All tests may assume +they are running in the JS shell environment. + +* REQUIREMENTS + +Python 3.6. This is already a standard requirement for building our tree. + +* RUNNING THE TESTS + +Basic usage: + + ./mach jit-test + +from the top of the checkout. Directly invoking + + python jit_test.py <path-to-js-shell> + +will also work. The progress bar shows [#tests passed, #tests failed, #tests +run] at the left. If all tests pass, the output is 'PASSED ALL'. The test suite +can be interrupted at any time with Ctrl+C and partial results will be printed. + +To run only the basic tests, not including the slow tests: + + ./mach jit-test <path-to-js-shell> basic + +For more options: + + ./mach jit-test -- -h + +or + + python jit_test.py -h + +for the jit-test harness options, or + + ./mach jit-test -h + +for the mach driver's options (eg --cgc). + +* CREATING NEW TESTS + +Simply create a JS file under the 'tests/' directory. Most tests should go in +'tests/basic/'. + +All tests are run with 'lib/prologue.js' included first on the command line. The +command line also creates a global variable 'libdir' that is set to the path +of the 'lib' directory. To include a file 'foo.js' from the lib directory in a +test case: + + load(libdir + 'foo.js') + +* TEST METALINES + +The first line of a test case can contain a special comment controlling how the +test is run. For example: + + // |jit-test| allow-oom; --no-threads + +The general format in EBNF is: + + metaline ::= cookie { item ";" } + cookie ::= "|jit-test|" + item ::= flag | attribute + + flag ::= "slow" | "allow-oom" | "valgrind" | "tz-pacific" | "debug" | + "--" switch + + attribute ::= name ":" value + name ::= "error" | "exitstatus" + value ::= <string> + switch ::= <string> + +The metaline may appear anywhere in the first line of the file: this allows it +to be placed inside any kind of comment. + +The meaning of the items: + + slow Test runs slowly. Do not run if the --no-slow option is given. + allow-oom If the test runs out of memory, it counts as passing. + valgrind Run test under valgrind. + tz-pacific Always run test with the Pacific time zone (TZ=PST8PDT). + + error The test should be considered to pass iff it throws the + given JS exception. + exitstatus The test should exit with the given status value (an integer). + + debug Run js with -d, whether --jitflags says to or not + --SWITCH Pass --SWITCH through to js + +* END diff --git a/js/src/jit-test/etc/generate-lookupswitch-tests.js b/js/src/jit-test/etc/generate-lookupswitch-tests.js new file mode 100644 index 0000000000..2fd2b73d1c --- /dev/null +++ b/js/src/jit-test/etc/generate-lookupswitch-tests.js @@ -0,0 +1,365 @@ + +/** + * A test case spec is an array of objects of the following kind: + * { 'match': Num|Str|Null, + * 'body': Num|Str|Null, + * 'fallthrough': Boolean } + * + * If the 'match' is null, then it represents a 'default:' + * If the 'match' is not null, it represents a 'case X:' where X is the value. + * If the 'body' is null, then it means that the case body is empty. Otherwise, + * it means that the case body is a single 'arr.push(V);' where "arr" is an input + * array to the function containing the switch statement, and V is the value. + * If 'fallthrough' is false, then the body is terminated with a break, otherwise + * it is not. + * + * So a spec: [{'match':3, 'body':null, 'fallthrough':false}, {'match':null, 'body':"foo", 'fallthrough':true}] + * Represents a switch function: + * function(x, arr) { + * switch(x) { + * case 3: + * break; + * default: + * arr.push('foo'); + * } + * } + * + * GenerateSpecPermutes generates a bunch of relevant specs, using the given case match-values, + * and appends them to result the array passed into it. + * + * InterpretSwitch takes a spec, a value, and a result array, and behaves as if the switch specified + * by the spec had been called on the value and the result array. + * + * VerifySwitchSpec is there but not used in the code. I was using it while testing the test case + * generator. It verifies that a switch spec is sane. + * + * RunSpec uses eval to run the test directly. It's not used currently. + * + * GenerateSwitchCode generates a string of the form "function NAME(x, arg) { .... }" which + * contains the switch modeled by its input spec. + * + * RunTest is there to be used from within the generated script. Its code is dumped out + * to the generated script text, and invoked there. + * + * Hope all of this makes some sort of sense. + * -kannan + */ + +/** HELPERS **/ + +function ASSERT(cond, msg) { assertEq(cond, true, msg); } + +function IsUndef(x) { return typeof(x) == 'undefined'; } +function IsNull(x) { return typeof(x) == 'object' && x == null; } +function IsNum(x) { return typeof(x) == 'number'; } +function IsStr(x) { return typeof(x) == 'string'; } +function IsBool(x) { return typeof(x) == 'boolean'; } + +function Repr(x) { + ASSERT(IsNum(x) || IsStr(x), "Repr"); + if(IsNum(x)) { return ""+x; } + else { return "'"+x+"'"; } +} + +function RandBool() { return Math.random() >= 0.5; } +function RandInt(max) { + if(IsUndef(max)) { max = 0x7fffffff; } + return (Math.random() * max)|0; +} + +var CHARS = "abcdefghijklmnopqrstuvywxyzABCDEFGHIJKLMNOPQRSTUVYWXYZ0123456789~!@#$%^&*()-_=+{}[]"; +function RandStr() { + var arr = []; + var len = Math.floor(Math.random() * 10) + 1; + for(var i = 0; i < len; i++) { + var c = Math.floor(Math.random() * CHARS.length); + arr.push(CHARS[c]); + } + return arr.join(''); +} + +function RandVal() { return RandBool() ? RandInt() : RandStr(); } + +/** + * Compare two arrays and ensure they are equal. + */ +function ArraysEqual(arr1, arr2) { + ASSERT(arr1.length == arr2.length, "Lengths not equal"); + for(var i = 0; i < arr1.length; i++) { + ASSERT(typeof(arr1[i]) == typeof(arr2[i]), "Types not equal for position " + i); + ASSERT(arr1[i] == arr2[i], "Values not equal for position " + i); + } +} + +function VerifySwitchSpec(spec) { + var foundDefault = undefined; + for(var i = 0; i < spec.length; i++) { + var caseSpec = spec[i], + match = caseSpec.match, + body = caseSpec.body, + fallthrough = caseSpec.fallthrough; + ASSERT(IsNum(match) || IsStr(match) || IsNull(match), "Invalid case match for " + i); + ASSERT(IsNum(body) || IsStr(body) || IsNull(body), "Invalid case body for " + i); + ASSERT(IsBool(fallthrough), "Invalid fallthrough for " + i); + + if(IsNull(match)) { + ASSERT(IsUndef(foundDefault), "Duplicate default found at " + i); + foundDefault = i; + } + } +} + +/** + * Do a manual interpretation of a particular spec, given an input + * and outputting to an output array. + */ +function InterpretSwitch(spec, input, outputArray) { + var foundMatch = undefined, foundDefault = undefined; + // Go through cases, trying to find a matching clause. + for(var i = 0; i < spec.length; i++) { + var caseSpec = spec[i], match = caseSpec.match; + + if(IsNull(match)) { + foundDefault = i; + continue; + } else if(match === input) { + foundMatch = i; + break; + } + } + // Select either matching clause or default. + var matchI = IsNum(foundMatch) ? foundMatch : foundDefault; + + // If match or default was found, interpret body from that point on. + if(IsNum(matchI)) { + for(var i = matchI; i < spec.length; i++) { + var caseSpec = spec[i], + match = caseSpec.match, + body = caseSpec.body, + fallthrough = caseSpec.fallthrough; + if(!IsNull(body)) { outputArray.push(body); } + if(!fallthrough) { break; } + } + } +} + +/** + * Generate the code string for a javascript function containing the + * switch specified by the spec, in pure JS syntax. + */ +function GenerateSwitchCode(spec, name) { + var lines = []; + if(!name) { name = ""; } + + lines.push("function "+name+"(x, arr) {"); + lines.push(" switch(x) {"); + for(var i = 0; i < spec.length; i++) { + var caseSpec = spec[i], + match = caseSpec.match, + body = caseSpec.body, + fallthrough = caseSpec.fallthrough; + + if(IsNull(match)) { lines.push(" default:"); } + else { lines.push(" case "+Repr(match)+":"); } + + if(!IsNull(body)) { lines.push(" arr.push("+Repr(body)+");"); } + if(!fallthrough) { lines.push(" break;"); } + } + lines.push(" }"); + lines.push("}"); + return lines.join("\n"); +} + +/** + * Generates all possible specs for a given set of case match values. + */ +function GenerateSpecPermutes(matchVals, resultArray) { + ASSERT((0 < matchVals.length) && (matchVals.length <= 5), "Invalid matchVals"); + var maxPermuteBody = (1 << matchVals.length) - 1; + for(var bod_pm = 0; bod_pm <= maxPermuteBody; bod_pm++) { + var maxPermuteFallthrough = (1 << matchVals.length) - 1; + + for(var ft_pm = 0; ft_pm <= maxPermuteFallthrough; ft_pm++) { + // use bod_m and ft_pm to decide the placement of null vs value bodies, + // and the placement of fallthroughs vs breaks. + // Empty bodies always fall through, so fallthrough bitmask 1s must be + // a subset of the body bitmask 1s. + if((bod_pm | ft_pm) != bod_pm) { + continue; + } + + var spec = []; + for(var k = 0; k < matchVals.length; k++) { + var match = matchVals[k]; + var body = ((bod_pm & (1 << k)) > 0) ? null : RandVal(); + var fallthrough = ((ft_pm & (1 << k)) > 0) ? true : false; + var caseSpec = {'match':match, 'body':body, 'fallthrough':fallthrough}; + spec.push(caseSpec); + } + + // Variant specs for following cases: + + // Default with empty body, fallthrough + GenerateDefaultPermutes(spec, null, true, resultArray); + // Default with nonempty body, fallthrough + GenerateDefaultPermutes(spec, RandVal(), true, resultArray); + // Default with nonempty body, break + GenerateDefaultPermutes(spec, RandVal(), false, resultArray); + } + } +} +function GenerateDefaultPermutes(spec, body, fallthrough, resultArray) { + if(spec.length <= 2) { + for(var i = 0; i <= spec.length; i++) { + var copy = CopySpec(spec); + if(IsNull(body)) { + copy.splice(i,0,{'match':null, 'body':null, 'fallthrough':true}); + } else { + copy.splice(i,0,{'match':null, 'body':body, 'fallthrough':fallthrough}); + } + resultArray.push(copy); + } + } else { + var posns = [0, Math.floor(spec.length / 2), spec.length]; + posns.forEach(function (i) { + var copy = CopySpec(spec); + if(IsNull(body)) { + copy.splice(i,0,{'match':null, 'body':null, 'fallthrough':true}); + } else { + copy.splice(i,0,{'match':null, 'body':body, 'fallthrough':fallthrough}); + } + resultArray.push(copy); + }); + } +} +function CopySpec(spec) { + var newSpec = []; + for(var i = 0; i < spec.length; i++) { + var caseSpec = spec[i]; + newSpec.push({'match':caseSpec.match, + 'body':caseSpec.body, + 'fallthrough':caseSpec.fallthrough}); + } + return newSpec; +} + + +function RunSpec(spec, matchVals) { + var code = GenerateSwitchCode(spec); + + // Generate roughly 200 inputs for the test case spec, exercising + // every match value, as well as 3 randomly generated values for every + // iteration of the match values. + var inputs = []; + while(inputs.length < 500) { + for(var i = 0; i < matchVals.length; i++) { inputs.push(matchVals[i]); } + for(var i = 0; i < 3; i++) { inputs.push(RandVal()); } + } + + // Interpret the lookupswitch with the inputs. + var interpResults = []; + for(var i = 0; i < inputs.length; i++) { + InterpretSwitch(spec, inputs[i], interpResults); + } + + // Run compiled lookupswitch with the inputs. + var fn = eval("_ = " + code); + print("Running spec: " + code); + var compileResults = RunCompiled(fn, inputs); + print("Done Running spec"); + + // Verify that they produce the same output. + ASSERT(interpResults.length == compileResults.length, "Length mismatch"); + for(var i = 0; i < interpResults.length; i++) { + ASSERT(interpResults[i] == compileResults[i], "Value mismatch"); + } +} +function RunCompiled(fn, inputs) { + var results = []; + var len = inputs.length; + for(var i = 0; i < len; i++) { fn(inputs[i], results); } + return results; +} + +function PrintSpec(spec, inputs, fname) { + var code = GenerateSwitchCode(spec, fname); + var input_s = fname + ".INPUTS = [" + inputs.map(Repr).join(', ') + "];"; + var spec_s = fname + ".SPEC = " + JSON.stringify(spec) + ";"; + print(code + "\n" + input_s + "\n" + spec_s); +} + +function RunTest(test) { + // Exercise every test case as well as one case which won't match with any of the + // ("But what if it randomly generates a string case match whose value is + // UNMATCHED_CASE?!", you ask incredulously. Well, RandStr limits itself to 11 chars. + // So there.) + var inputs = test.INPUTS; + inputs.push("UNMATCHED_CASE"); + var spec = test.SPEC; + + var results1 = []; + for(var i = 0; i < 80; i++) { + for(var j = 0; j < inputs.length; j++) { + test(inputs[j], results1); + } + } + + var results2 = []; + for(var i = 0; i < 80; i++) { + for(var j = 0; j < inputs.length; j++) { + InterpretSwitch(spec, inputs[j], results2); + } + } + ArraysEqual(results1, results2); +} + +// NOTES: +// * RunTest is used within the generated test script. +// * InterpretSwitch is printed out into the generated test script. + +print("/////////////////////////////////////////"); +print("// This is a generated file!"); +print("// See jit-tests/etc/generate-lookupswitch-tests.js for the code"); +print("// that generated this code!"); +print("/////////////////////////////////////////"); +print(""); +print("/////////////////////////////////////////"); +print("// PRELUDE //"); +print("/////////////////////////////////////////"); +print(""); +print("// Avoid eager compilation of the global-scope."); +print("try{} catch (x) {};"); +print(""); +print(ASSERT); +print(IsNull); +print(IsNum); +print(ArraysEqual); +print(InterpretSwitch); +print(RunTest); +print(""); +print("/////////////////////////////////////////"); +print("// TEST CASES //"); +print("/////////////////////////////////////////"); +print(""); +print("var TESTS = [];"); +var MATCH_SETS = [["foo", "bar", "zing"]]; +var count = 0; +for(var i = 0; i < MATCH_SETS.length; i++) { + var matchSet = MATCH_SETS[i]; + var specs = []; + GenerateSpecPermutes(matchSet, specs); + for(var j = 0; j < specs.length; j++) { + count++; + PrintSpec(specs[j], matchSet.slice(), 'test_'+count); + print("TESTS.push(test_"+count+");\n"); + } +} + +print(""); +print("/////////////////////////////////////////"); +print("// RUNNER //"); +print("/////////////////////////////////////////"); +print(""); +print("for(var i = 0; i < TESTS.length; i++) {"); +print(" RunTest(TESTS[i]);"); +print("}"); diff --git a/js/src/jit-test/etc/generate-nosuchproperty-tests.js b/js/src/jit-test/etc/generate-nosuchproperty-tests.js new file mode 100644 index 0000000000..a0bb3e47db --- /dev/null +++ b/js/src/jit-test/etc/generate-nosuchproperty-tests.js @@ -0,0 +1,78 @@ + +// This code generates the test cases jit-test/tests/baseline/no-such-property-getprop.js +// +// In particular, it generates the testChain_<N>_<I>() and runChain_<N>_<I>() functions +// at the tail of the file. + +var TEST_CASE_FUNCS = []; +function runChain_NNNN_DDDD(obj) { + var sum = 0; + for (var i = 0; i < 100; i++) + sum += obj.foo; + return sum; +} +function testChain_NNNN_DDDD() { + var obj = createTower(NNNN); + assertEq(runChain_NNNN_DDDD(obj), NaN); + updateChain(obj, DDDD, 'foo', 9); + assertEq(runChain_NNNN_DDDD(obj), 900); +} +function generateTestCase(n, d) { + var runFuncName = "runChain_" + n + "_" + d; + var testFuncName = "testChain_" + n + "_" + d; + TEST_CASE_FUNCS.push(testFuncName); + + print("//// Test chain of length " + n + " with late-property-addition at depth " + d); + print(String(runChain_NNNN_DDDD).replace(/NNNN/g, ''+n).replace(/DDDD/g, ''+d)); + print(String(testChain_NNNN_DDDD).replace(/NNNN/g, ''+n).replace(/DDDD/g, ''+d)); + print(""); +} + +// Helper function to create an object with a proto-chain of length N. +function createTower(n) { + var result = Object.create(null); + for (var i = 0; i < n; i++) + result = Object.create(result); + return result; +} + +function updateChain(obj, depth, prop, value) { + // Walk down the proto chain |depth| iterations and set |prop| to |value|. + var cur = obj; + for (var i = 0; i < depth; i++) + cur = Object.getPrototypeOf(cur); + + var desc = {value:value, writable:true, configurable:true, enumerable:true}; + Object.defineProperty(cur, prop, desc); +} + +print("/////////////////////////////////////////"); +print("// This is a generated file!"); +print("// See jit-tests/etc/generate-nosuchproperty-tests.js for the code"); +print("// that generated this code!"); +print("/////////////////////////////////////////"); +print(""); +print("/////////////////////////////////////////"); +print("// PRELUDE //"); +print("/////////////////////////////////////////"); +print(""); +print(createTower); +print(updateChain); +print(""); +print("/////////////////////////////////////////"); +print("// TEST CASES //"); +print("/////////////////////////////////////////"); +print(""); +for (var n = 0; n <= 10; n++) { + for (var d = 0; d <= n; d++) { + generateTestCase(n, d); + } +} + +print(""); +print("/////////////////////////////////////////"); +print("// RUNNER //"); +print("/////////////////////////////////////////"); +print(""); +for (var i = 0; i < TEST_CASE_FUNCS.length; i++) + print(TEST_CASE_FUNCS[i] + "();"); 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/Cargo.lock b/js/src/jit-test/etc/wasm/generate-spectests/Cargo.lock new file mode 100644 index 0000000000..fc48ba7a55 --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/Cargo.lock @@ -0,0 +1,266 @@ +# 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.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eff853c4f09eec94d76af527eddad4e9de13b11d6286a1ef7134bc30135a2b7" +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 = "55.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4984d3e1406571f4930ba5cf79bd70f75f41d0e87e17506e0bd19b0e5d085f05" +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..4423a31077 --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/README.md @@ -0,0 +1,62 @@ +# 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 + +```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..7db5f73e43 --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/config-lock.toml @@ -0,0 +1,31 @@ +[[repos]] +name = 'spec' +commit = 'b1fbe1a8' + +[[repos]] +name = 'threads' +commit = '85b562cd' + +[[repos]] +name = 'simd' +commit = 'a78b98a6' + +[[repos]] +name = 'exception-handling' +commit = '76419ef8' + +[[repos]] +name = 'memory64' +commit = '8d8f532d' + +[[repos]] +name = 'function-references' +commit = 'c4eef9a8' + +[[repos]] +name = 'relaxed-simd' +commit = 'c4c9ddaf' + +[[repos]] +name = 'extended-const' +commit = 'dd72ab96' 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..9341a70ab7 --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/config.toml @@ -0,0 +1,114 @@ +# 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 = "threads" +url = "https://github.com/WebAssembly/threads" +branch = "main" +parent = "spec" +excluded_tests = [ + "atomic.wast", + # testing that multi-value isn't implemented (func.wast:492) + "func.wast", + # testing that multi-value isn't implemented (type.wast:52) + "type.wast" +] +# Skip in WPT where we can't guard on features being enabled +skip_wpt = true +# Skip in jit-test when it's not enabled +directive = "; skip-if: !wasmThreadsEnabled()" + +[[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; skip-if: !wasmExceptionsEnabled()" +excluded_tests = [ + # This gets included due to divergence between the proposal repo and the + # upstream spec, it can be removed when the repo is rebased. + "global.wast" +] + +[[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 = [ + # return_call_ref not implemented + "return_call_ref.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", +] + +[[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; skip-if: !wasmExtendedConstEnabled()" +excluded_tests = [] 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..4329cc10fb --- /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 = "55.0.0" 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..c6516cb0d0 --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/convert.rs @@ -0,0 +1,672 @@ +/* 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_string(id.name())) + )?; + } + + *current_instance = Some(next_instance); + } + Wat(_) => { + write!(out, "// unsupported quote wat")?; + } + Register { + span: _, + name, + module, + } => { + let instanceish = module + .map(|x| format!("`{}`", escape_template_string(x.name()))) + .unwrap_or_else(|| format!("${}", current_instance.unwrap())); + + writeln!( + out, + "register({}, `{}`);", + instanceish, + escape_template_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_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_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_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_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_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))?; + } + } + + Ok(()) +} + +fn escape_template_string(text: &str) -> 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() && 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 +} + +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_string(opened_module)); + } + Ok(escape_template_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_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_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_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_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::Func) => format!("value('anyfunc', null)"), + Some(wast::core::HeapType::Extern) => format!("value('externref', null)"), + other => bail!( + "couldn't convert ref.null {:?} to a js assertion value", + other + ), + }, + RefExtern(x) => format!("value('externref', externref({}))", x), + 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), + 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..ba615e05dd --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/harness.js @@ -0,0 +1,360 @@ +"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; + } +} + +let externrefs = {}; +let externsym = Symbol("externref"); +function externref(s) { + if (!(s in externrefs)) externrefs[s] = { [externsym]: s }; + return externrefs[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; +} + +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(); + assertEq("normal return", "trap"); + } catch (err) { + assertEq( + err instanceof WebAssembly.RuntimeError, + true, + "expected trap", + ); + } +} + +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 (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 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 (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..1d47365f19 --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/out.rs @@ -0,0 +1,180 @@ +/* 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 body_string = + body.output(line_remaining - "invoke".len() - instance.len() - name.len()); + 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..d0de76b1ab --- /dev/null +++ b/js/src/jit-test/etc/wasm/spec-tests.patch @@ -0,0 +1,784 @@ +# HG changeset patch +# User Ryan Hunt <rhunt@eqrion.net> +# Date 1634962581 0 +# Sat Oct 23 04:16:21 2021 +0000 +# Node ID 40a38b62293c892ef4e397c43179321edccea2c3 +# Parent bb4199affde55df21fd07f2fbf65155bc60a0943 +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. + +user: Lars Hansen +Differential Revision: https://phabricator.services.mozilla.com/D129248 + +2. Bug 1747450 - simd has moved to spec and needs per-test skips. +user: Ryan Hunt + +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`); +- +-// ./test/core/align64.wast:866 +-assert_return(() => invoke($24, `load`, [65532n]), [value("i32", 0)]); ++// 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)]); ++} +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,17 @@ if (!wasmIsSupported()) { + quit(); + } + ++function partialOobWriteMayWritePartialData() { ++ let cfg = getBuildConfiguration(); ++ let arm_native = cfg["arm"] && !cfg["arm-simulator"]; ++ let arm64_native = cfg["arm64"] && !cfg["arm64-simulator"]; ++ return arm_native || arm64_native; ++} ++ ++let cfg = getBuildConfiguration(); ++let native_arm = cfg["arm"] && !cfg["arm-simulator"]; ++let native_arm64 = cfg["arm64"] && !cfg["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_u`, [-3n]), `out of bounds memory acces + // ./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)]); +- +-// ./test/core/memory_trap64.wast:269 +-assert_return(() => invoke($1, `i64.load`, [0n]), [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), ++ ]); ++} +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/call_ref.wast.js b/js/src/jit-test/tests/wasm/spec/function-references/call_ref.wast.js +TODO non-nullable references not supported in globals +--- a/js/src/jit-test/tests/wasm/spec/function-references/call_ref.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/function-references/call_ref.wast.js +@@ -39,74 +39,6 @@ let $0 = instantiate(`(module + (func (export "null") (result i32) + (call_ref $$ii (i32.const 1) (ref.null $$ii)) + ) +- +- ;; Recursion +- +- (type $$ll (func (param i64) (result i64))) +- (type $$lll (func (param i64 i64) (result i64))) +- +- (elem declare func $$fac) +- (global $$fac (ref $$ll) (ref.func $$fac)) +- +- (func $$fac (export "fac") (type $$ll) +- (if (result i64) (i64.eqz (local.get 0)) +- (then (i64.const 1)) +- (else +- (i64.mul +- (local.get 0) +- (call_ref $$ll (i64.sub (local.get 0) (i64.const 1)) (global.get $$fac)) +- ) +- ) +- ) +- ) +- +- (elem declare func $$fac-acc) +- (global $$fac-acc (ref $$lll) (ref.func $$fac-acc)) +- +- (func $$fac-acc (export "fac-acc") (type $$lll) +- (if (result i64) (i64.eqz (local.get 0)) +- (then (local.get 1)) +- (else +- (call_ref $$lll +- (i64.sub (local.get 0) (i64.const 1)) +- (i64.mul (local.get 0) (local.get 1)) +- (global.get $$fac-acc) +- ) +- ) +- ) +- ) +- +- (elem declare func $$fib) +- (global $$fib (ref $$ll) (ref.func $$fib)) +- +- (func $$fib (export "fib") (type $$ll) +- (if (result i64) (i64.le_u (local.get 0) (i64.const 1)) +- (then (i64.const 1)) +- (else +- (i64.add +- (call_ref $$ll (i64.sub (local.get 0) (i64.const 2)) (global.get $$fib)) +- (call_ref $$ll (i64.sub (local.get 0) (i64.const 1)) (global.get $$fib)) +- ) +- ) +- ) +- ) +- +- (elem declare func $$even $$odd) +- (global $$even (ref $$ll) (ref.func $$even)) +- (global $$odd (ref $$ll) (ref.func $$odd)) +- +- (func $$even (export "even") (type $$ll) +- (if (result i64) (i64.eqz (local.get 0)) +- (then (i64.const 44)) +- (else (call_ref $$ll (i64.sub (local.get 0) (i64.const 1)) (global.get $$odd))) +- ) +- ) +- (func $$odd (export "odd") (type $$ll) +- (if (result i64) (i64.eqz (local.get 0)) +- (then (i64.const 99)) +- (else (call_ref $$ll (i64.sub (local.get 0) (i64.const 1)) (global.get $$even))) +- ) +- ) + )`); + + // ./test/core/call_ref.wast:94 +@@ -118,69 +50,6 @@ assert_return(() => invoke($0, `run`, [3 + // ./test/core/call_ref.wast:97 + assert_trap(() => invoke($0, `null`, []), `null function`); + +-// ./test/core/call_ref.wast:99 +-assert_return(() => invoke($0, `fac`, [0n]), [value("i64", 1n)]); +- +-// ./test/core/call_ref.wast:100 +-assert_return(() => invoke($0, `fac`, [1n]), [value("i64", 1n)]); +- +-// ./test/core/call_ref.wast:101 +-assert_return(() => invoke($0, `fac`, [5n]), [value("i64", 120n)]); +- +-// ./test/core/call_ref.wast:102 +-assert_return(() => invoke($0, `fac`, [25n]), [value("i64", 7034535277573963776n)]); +- +-// ./test/core/call_ref.wast:103 +-assert_return(() => invoke($0, `fac-acc`, [0n, 1n]), [value("i64", 1n)]); +- +-// ./test/core/call_ref.wast:104 +-assert_return(() => invoke($0, `fac-acc`, [1n, 1n]), [value("i64", 1n)]); +- +-// ./test/core/call_ref.wast:105 +-assert_return(() => invoke($0, `fac-acc`, [5n, 1n]), [value("i64", 120n)]); +- +-// ./test/core/call_ref.wast:106 +-assert_return(() => invoke($0, `fac-acc`, [25n, 1n]), [value("i64", 7034535277573963776n)]); +- +-// ./test/core/call_ref.wast:111 +-assert_return(() => invoke($0, `fib`, [0n]), [value("i64", 1n)]); +- +-// ./test/core/call_ref.wast:112 +-assert_return(() => invoke($0, `fib`, [1n]), [value("i64", 1n)]); +- +-// ./test/core/call_ref.wast:113 +-assert_return(() => invoke($0, `fib`, [2n]), [value("i64", 2n)]); +- +-// ./test/core/call_ref.wast:114 +-assert_return(() => invoke($0, `fib`, [5n]), [value("i64", 8n)]); +- +-// ./test/core/call_ref.wast:115 +-assert_return(() => invoke($0, `fib`, [20n]), [value("i64", 10946n)]); +- +-// ./test/core/call_ref.wast:117 +-assert_return(() => invoke($0, `even`, [0n]), [value("i64", 44n)]); +- +-// ./test/core/call_ref.wast:118 +-assert_return(() => invoke($0, `even`, [1n]), [value("i64", 99n)]); +- +-// ./test/core/call_ref.wast:119 +-assert_return(() => invoke($0, `even`, [100n]), [value("i64", 44n)]); +- +-// ./test/core/call_ref.wast:120 +-assert_return(() => invoke($0, `even`, [77n]), [value("i64", 99n)]); +- +-// ./test/core/call_ref.wast:121 +-assert_return(() => invoke($0, `odd`, [0n]), [value("i64", 99n)]); +- +-// ./test/core/call_ref.wast:122 +-assert_return(() => invoke($0, `odd`, [1n]), [value("i64", 44n)]); +- +-// ./test/core/call_ref.wast:123 +-assert_return(() => invoke($0, `odd`, [200n]), [value("i64", 99n)]); +- +-// ./test/core/call_ref.wast:124 +-assert_return(() => invoke($0, `odd`, [77n]), [value("i64", 44n)]); +- + // ./test/core/call_ref.wast:129 + let $1 = instantiate(`(module + (type $$t (func)) +diff --git a/js/src/jit-test/tests/wasm/spec/function-references/local_get.wast.js b/js/src/jit-test/tests/wasm/spec/function-references/local_get.wast.js +TODO Error: globals are of different type +--- a/js/src/jit-test/tests/wasm/spec/function-references/local_get.wast.js ++++ b/js/src/jit-test/tests/wasm/spec/function-references/local_get.wast.js +@@ -297,18 +297,6 @@ let $1 = instantiate(`(module + ) + )`); + +-// ./test/core/local_get.wast:248 +-assert_return(() => invoke($1, `get-after-set`, [externref(1)]), [value('externref', externref(1))]); +- +-// ./test/core/local_get.wast:249 +-assert_return(() => invoke($1, `get-after-tee`, [externref(2)]), [value('externref', externref(2))]); +- +-// ./test/core/local_get.wast:250 +-assert_return( +- () => invoke($1, `get-in-block-after-set`, [externref(3)]), +- [value('externref', externref(3))], +-); +- + // ./test/core/local_get.wast:252 + assert_invalid( + () => instantiate(`(module (func $$uninit (local $$x (ref extern)) (drop (local.get $$x))))`), +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/jit_test.py b/js/src/jit-test/jit_test.py new file mode 100755 index 0000000000..d9f08a7e15 --- /dev/null +++ b/js/src/jit-test/jit_test.py @@ -0,0 +1,576 @@ +#!/usr/bin/env python +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import math +import os +import platform +import posixpath +import shlex +import subprocess +import sys +import traceback + +read_input = input +if sys.version_info.major == 2: + read_input = raw_input + + +def add_tests_dir_to_path(): + from os.path import dirname, exists, join, realpath + + js_src_dir = dirname(dirname(realpath(sys.argv[0]))) + assert exists(join(js_src_dir, "jsapi.h")) + sys.path.insert(0, join(js_src_dir, "tests")) + + +add_tests_dir_to_path() + +from lib import jittests +from lib.tempfile import TemporaryDirectory +from lib.tests import ( + change_env, + get_cpu_count, + get_environment_overlay, + get_jitflags, + valid_jitflags, +) + + +def which(name): + if name.find(os.path.sep) != -1: + return os.path.abspath(name) + + for path in os.environ["PATH"].split(os.pathsep): + full = os.path.join(path, name) + if os.path.exists(full): + return os.path.abspath(full) + + return name + + +def choose_item(jobs, max_items, display): + job_count = len(jobs) + + # Don't present a choice if there are too many tests + if job_count > max_items: + raise Exception("Too many jobs.") + + for i, job in enumerate(jobs, 1): + print("{}) {}".format(i, display(job))) + + item = read_input("Which one:\n") + try: + item = int(item) + if item > job_count or item < 1: + raise Exception("Input isn't between 1 and {}".format(job_count)) + except ValueError: + raise Exception("Unrecognized input") + + return jobs[item - 1] + + +def main(argv): + # The [TESTS] optional arguments are paths of test files relative + # to the jit-test/tests directory. + import argparse + + op = argparse.ArgumentParser(description="Run jit-test JS shell tests") + op.add_argument( + "-s", + "--show-cmd", + dest="show_cmd", + action="store_true", + help="show js shell command run", + ) + op.add_argument( + "-f", + "--show-failed-cmd", + dest="show_failed", + action="store_true", + help="show command lines of failed tests", + ) + op.add_argument( + "-o", + "--show-output", + dest="show_output", + action="store_true", + help="show output from js shell", + ) + op.add_argument( + "-F", + "--failed-only", + dest="failed_only", + action="store_true", + help="if --show-output is given, only print output for" " failed tests", + ) + op.add_argument( + "--no-show-failed", + dest="no_show_failed", + action="store_true", + help="don't print output for failed tests" " (no-op with --show-output)", + ) + op.add_argument( + "-x", + "--exclude", + dest="exclude", + default=[], + action="append", + help="exclude given test dir or path", + ) + op.add_argument( + "--exclude-from", + dest="exclude_from", + type=str, + help="exclude each test dir or path in FILE", + ) + op.add_argument( + "--slow", + dest="run_slow", + action="store_true", + help="also run tests marked as slow", + ) + op.add_argument( + "--no-slow", + dest="run_slow", + action="store_false", + help="do not run tests marked as slow (the default)", + ) + op.add_argument( + "-t", + "--timeout", + dest="timeout", + type=float, + default=150.0, + help="set test timeout in seconds", + ) + op.add_argument( + "--no-progress", + dest="hide_progress", + action="store_true", + help="hide progress bar", + ) + op.add_argument( + "--tinderbox", + dest="format", + action="store_const", + const="automation", + help="Use automation-parseable output format", + ) + op.add_argument( + "--format", + dest="format", + default="none", + choices=("automation", "none"), + help="Output format (default %(default)s).", + ) + op.add_argument( + "--args", + dest="shell_args", + metavar="ARGS", + default="", + help="extra args to pass to the JS shell", + ) + op.add_argument( + "--feature-args", + dest="feature_args", + metavar="ARGS", + default="", + help="even more args to pass to the JS shell " + "(for compatibility with jstests.py)", + ) + op.add_argument( + "-w", + "--write-failures", + dest="write_failures", + metavar="FILE", + help="Write a list of failed tests to [FILE]", + ) + op.add_argument( + "-C", + "--check-output", + action="store_true", + dest="check_output", + help="Run tests to check output for different jit-flags", + ) + op.add_argument( + "-r", + "--read-tests", + dest="read_tests", + metavar="FILE", + help="Run test files listed in [FILE]", + ) + op.add_argument( + "-R", + "--retest", + dest="retest", + metavar="FILE", + help="Retest using test list file [FILE]", + ) + op.add_argument( + "-g", + "--debug", + action="store_const", + const="gdb", + dest="debugger", + help="Run a single test under the gdb debugger", + ) + op.add_argument( + "-G", + "--debug-rr", + action="store_const", + const="rr", + dest="debugger", + help="Run a single test under the rr debugger", + ) + op.add_argument( + "--debugger", type=str, help="Run a single test under the specified debugger" + ) + op.add_argument( + "--valgrind", + dest="valgrind", + action="store_true", + help="Enable the |valgrind| flag, if valgrind is in $PATH.", + ) + op.add_argument( + "--unusable-error-status", + action="store_true", + help="Ignore incorrect exit status on tests that should return nonzero.", + ) + op.add_argument( + "--valgrind-all", + dest="valgrind_all", + action="store_true", + help="Run all tests with valgrind, if valgrind is in $PATH.", + ) + op.add_argument( + "--write-failure-output", + dest="write_failure_output", + action="store_true", + help="With --write-failures=FILE, additionally write the" + " output of failed tests to [FILE]", + ) + op.add_argument( + "--jitflags", + dest="jitflags", + default="none", + choices=valid_jitflags(), + help="IonMonkey option combinations (default %(default)s).", + ) + op.add_argument( + "--ion", + dest="jitflags", + action="store_const", + const="ion", + help="Run tests once with --ion-eager and once with" + " --baseline-eager (equivalent to --jitflags=ion)", + ) + op.add_argument( + "--no-xdr", + dest="use_xdr", + action="store_false", + help="Whether to disable caching of self-hosted parsed content in XDR format.", + ) + op.add_argument( + "--tbpl", + dest="jitflags", + action="store_const", + const="all", + help="Run tests with all IonMonkey option combinations" + " (equivalent to --jitflags=all)", + ) + op.add_argument( + "-j", + "--worker-count", + dest="max_jobs", + type=int, + default=max(1, get_cpu_count()), + help="Number of tests to run in parallel (default %(default)s).", + ) + op.add_argument( + "--remote", action="store_true", help="Run tests on a remote device" + ) + op.add_argument( + "--deviceIP", + action="store", + type=str, + dest="device_ip", + help="IP address of remote device to test", + ) + op.add_argument( + "--devicePort", + action="store", + type=int, + dest="device_port", + default=20701, + help="port of remote device to test", + ) + op.add_argument( + "--deviceSerial", + action="store", + type=str, + dest="device_serial", + default=None, + help="ADB device serial number of remote device to test", + ) + op.add_argument( + "--remoteTestRoot", + dest="remote_test_root", + action="store", + type=str, + default="/data/local/tmp/test_root", + help="The remote directory to use as test root" " (e.g. %(default)s)", + ) + op.add_argument( + "--localLib", + dest="local_lib", + action="store", + type=str, + help="The location of libraries to push -- preferably" " stripped", + ) + op.add_argument( + "--repeat", type=int, default=1, help="Repeat tests the given number of times." + ) + op.add_argument("--this-chunk", type=int, default=1, help="The test chunk to run.") + op.add_argument( + "--total-chunks", type=int, default=1, help="The total number of test chunks." + ) + op.add_argument( + "--ignore-timeouts", + dest="ignore_timeouts", + metavar="FILE", + help="Ignore timeouts of tests listed in [FILE]", + ) + op.add_argument( + "--retry-remote-timeouts", + dest="timeout_retry", + type=int, + default=1, + help="Number of time to retry timeout on remote devices", + ) + op.add_argument( + "--test-reflect-stringify", + dest="test_reflect_stringify", + help="instead of running tests, use them to test the " + "Reflect.stringify code in specified file", + ) + # --enable-webrender is ignored as it is not relevant for JIT + # tests, but is required for harness compatibility. + op.add_argument( + "--enable-webrender", + action="store_true", + dest="enable_webrender", + default=False, + help=argparse.SUPPRESS, + ) + op.add_argument("js_shell", metavar="JS_SHELL", help="JS shell to run tests with") + op.add_argument( + "-z", "--gc-zeal", help="GC zeal mode to use when running the shell" + ) + + options, test_args = op.parse_known_args(argv) + js_shell = which(options.js_shell) + test_environment = get_environment_overlay(js_shell, options.gc_zeal) + + if not (os.path.isfile(js_shell) and os.access(js_shell, os.X_OK)): + if ( + platform.system() != "Windows" + or os.path.isfile(js_shell) + or not os.path.isfile(js_shell + ".exe") + or not os.access(js_shell + ".exe", os.X_OK) + ): + op.error("shell is not executable: " + js_shell) + + if options.retest: + options.read_tests = options.retest + options.write_failures = options.retest + + test_list = [] + read_all = True + + if test_args: + read_all = False + for arg in test_args: + test_list += jittests.find_tests(arg) + + if options.read_tests: + read_all = False + try: + f = open(options.read_tests) + for line in f: + test_list.append(os.path.join(jittests.TEST_DIR, line.strip("\n"))) + f.close() + except IOError: + if options.retest: + read_all = True + else: + sys.stderr.write( + "Exception thrown trying to read test file" + " '{}'\n".format(options.read_tests) + ) + traceback.print_exc() + sys.stderr.write("---\n") + + if read_all: + test_list = jittests.find_tests() + + if options.exclude_from: + with open(options.exclude_from) as fh: + for line in fh: + line_exclude = line.strip() + if not line_exclude.startswith("#") and len(line_exclude): + options.exclude.append(line_exclude) + + if options.exclude: + exclude_list = [] + for exclude in options.exclude: + exclude_list += jittests.find_tests(exclude) + test_list = [test for test in test_list if test not in set(exclude_list)] + + if not test_list: + print("No tests found matching command line arguments.", file=sys.stderr) + sys.exit(0) + + test_list = [jittests.JitTest.from_file(_, options) for _ in test_list] + + if not options.run_slow: + test_list = [_ for _ in test_list if not _.slow] + + if options.test_reflect_stringify is not None: + for test in test_list: + test.test_reflect_stringify = options.test_reflect_stringify + + # If chunking is enabled, determine which tests are part of this chunk. + # This code was adapted from testing/mochitest/runtestsremote.py. + if options.total_chunks > 1: + total_tests = len(test_list) + tests_per_chunk = math.ceil(total_tests / float(options.total_chunks)) + start = int(round((options.this_chunk - 1) * tests_per_chunk)) + end = int(round(options.this_chunk * tests_per_chunk)) + test_list = test_list[start:end] + + if not test_list: + print( + "No tests found matching command line arguments after filtering.", + file=sys.stderr, + ) + sys.exit(0) + + # The full test list is ready. Now create copies for each JIT configuration. + test_flags = get_jitflags(options.jitflags) + + test_list = [_ for test in test_list for _ in test.copy_variants(test_flags)] + + job_list = (test for test in test_list) + job_count = len(test_list) + + if options.repeat: + + def repeat_copy(job_list_generator, repeat): + job_list = list(job_list_generator) + for i in range(repeat): + for test in job_list: + if i == 0: + yield test + else: + yield test.copy() + + job_list = repeat_copy(job_list, options.repeat) + job_count *= options.repeat + + if options.ignore_timeouts: + read_all = False + try: + with open(options.ignore_timeouts) as f: + ignore = set() + for line in f.readlines(): + path = line.strip("\n") + ignore.add(path) + options.ignore_timeouts = ignore + except IOError: + sys.exit("Error reading file: " + options.ignore_timeouts) + else: + options.ignore_timeouts = set() + + prefix = ( + [js_shell] + shlex.split(options.shell_args) + shlex.split(options.feature_args) + ) + prologue = os.path.join(jittests.LIB_DIR, "prologue.js") + if options.remote: + prologue = posixpath.join(options.remote_test_root, "lib", "prologue.js") + + prefix += ["-p", prologue] + + if options.debugger: + if job_count > 1: + print( + "Multiple tests match command line" + " arguments, debugger can only run one" + ) + jobs = list(job_list) + + def display_job(job): + flags = "" + if len(job.jitflags) != 0: + flags = "({})".format(" ".join(job.jitflags)) + return "{} {}".format(job.path, flags) + + try: + tc = choose_item(jobs, max_items=50, display=display_job) + except Exception as e: + sys.exit(str(e)) + else: + tc = next(job_list) + + if options.debugger == "gdb": + debug_cmd = ["gdb", "--args"] + elif options.debugger == "lldb": + debug_cmd = ["lldb", "--"] + elif options.debugger == "rr": + debug_cmd = ["rr", "record"] + else: + debug_cmd = options.debugger.split() + + with change_env(test_environment): + with TemporaryDirectory() as tempdir: + if options.debugger == "rr": + subprocess.call( + debug_cmd + + tc.command( + prefix, jittests.LIB_DIR, jittests.MODULE_DIR, tempdir + ) + ) + os.execvp("rr", ["rr", "replay"]) + else: + os.execvp( + debug_cmd[0], + debug_cmd + + tc.command( + prefix, jittests.LIB_DIR, jittests.MODULE_DIR, tempdir + ), + ) + sys.exit() + + try: + ok = None + if options.remote: + ok = jittests.run_tests(job_list, job_count, prefix, options, remote=True) + else: + with change_env(test_environment): + ok = jittests.run_tests(job_list, job_count, prefix, options) + if not ok: + sys.exit(2) + except OSError: + if not os.path.exists(prefix[0]): + print( + "JS shell argument: file does not exist:" " '{}'".format(prefix[0]), + file=sys.stderr, + ) + sys.exit(1) + else: + raise + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/js/src/jit-test/lib/adhoc-multiplatform-test.js b/js/src/jit-test/lib/adhoc-multiplatform-test.js new file mode 100644 index 0000000000..511edd47a7 --- /dev/null +++ b/js/src/jit-test/lib/adhoc-multiplatform-test.js @@ -0,0 +1,259 @@ + +// This file provides a version of the functions +// +// codegenTestX64_adhoc (src/jit-test/lib/codegen-x64-test.js) +// codegenTestX86_adhoc (src/jit-test/lib/codegen-x86-test.js) +// codegenTestARM64_adhoc (src/jit-test/lib/codegen-arm64-test.js) +// (and the equivalent arm(32) function) +// +// generalised so the output can be specified for all 4 targets in one place. +// +// Usage: +// codegenTestMultiplatform_adhoc(module_text, export_name, +// expectedAllTargets, options = {}) +// +// where +// `expectedAllTargets` states the expected-output regexps for each target, +// thusly: +// +// {x64: 'text', x86: 'text', arm64: 'text', arm: 'text'} +// +// The arm(32) expected output is optional. The other 3 must be present. +// +// Each 'text' is a string that represents a regular expression, possibly +// with newlines, representing the instruction or instructions we are looking +// for for the operator. Spaces in the expected-pattern are arbitrary, we +// preprocess the pattern to replace any space string with \s+. Lines are +// separated by newlines and leading and trailing spaces are stripped. +// Pattern strings may be empty, denoting "no instruction(s)". +// +// options specifies options thusly: +// +// instanceBox: if present, an object with a `value` property that will +// receive the constructed instance +// +// log: for debugging -- print the disassembly and other info helpful to +// resolving test failures. This is also printed on a test failure +// regardless of the log setting. +// +// features: this is passed on verbatim to wasmEvalText, +// as its third argument. +// +// no_prefix: by default, the required pattern must be immediately preceded +// by `<target>_prefix`, and this is checked. Setting this to +// true skips the check. Try not to use this. +// +// no_suffix: by default, the required pattern must be immediately followed +// by `<target>_suffix`, and this is checked. Setting this to +// true skips the check. Try not to use this. +// +// no_prefix/no_suffix apply to all 4 targets. Per-target overrides are +// supported, by putting them in a suitably tagged sub-object, eg: +// options = {x86: {no_prefix: true}} + +load(libdir + "codegen-test-common.js"); + +// Architectures supported by this script. +const knownArchs = ["x64", "x86", "arm64", "arm"]; + +// Architectures for which `expectedAllTargets` must supply an expected result. +const requiredArchs = ["x64", "x86", "arm64"]; + +// These define the end-of-prologue ("prefix") and start-of-epilogue +// ("suffix") to be matched. +const prefixAndSuffix = + {x64: { + prefix: `48 89 e5 mov %rsp, %rbp`, + suffix: `5d pop %rbp` + }, + x86: { + // The mov to e[ac]x is debug code, inserted by the register + // allocator to clobber e[ac]x before a move group. But it is only + // present if there is a move group there. + prefix: `8b ec mov %esp, %ebp( + b. ef be ad de mov \\$0xDEADBEEF, %e.x)?`, + // `.bp` because zydis chooses `rbp` even on 32-bit systems. + suffix: `5d pop %.bp` + }, + arm64: { + prefix: `910003fd mov x29, sp + 910003fc mov x28, sp`, + suffix: `f94003fd ldr x29, \\[sp\\]` + }, + arm: { + prefix: `e52db004 str fp, \\[sp, #-4\\]! + e1a0b00d mov fp, sp`, + suffix: `e49db004 ldr fp, \\[sp\\], #\\+4` + } + }; + +// The options object may a mix of generic (all-targets) options and contain +// sub-objects containing arch-specific options, for example: +// +// {a_generic_option: 1337, x86: {no_prefix:true}, arm64: {foo:4771}} +// +// promoteArchSpecificOptions lifts options for `archName` to the top level +// and deletes *all* arch-specific subobjects, hence producing the final +// to-be-used option set. For the above example, if `archName` is "x86" we +// get: +// +// {a_generic_option: 1337, no_prefix: true} +// +function promoteArchSpecificOptions(options, archName) { + assertEq(true, knownArchs.some(a => archName == a)); + if (options.hasOwnProperty(archName)) { + let archOptions = options[archName]; + for (optName in archOptions) { + options[optName] = archOptions[optName]; + if (options.log) { + print("---- adding " + archName + "-specific option {" + + optName + ":" + archOptions[optName] + "}"); + } + } + } + for (a of knownArchs) { + delete options[a]; + } + if (options.log) { + print("---- final options"); + for (optName in options) { + print("{" + optName + ":" + options[optName] + "}"); + } + } + return options; +} + +// Main test function. See comments at top of this file. +function codegenTestMultiplatform_adhoc(module_text, export_name, + expectedAllTargets, options = {}) { + assertEq(hasDisassembler(), true); + + // Check that we've been provided with an expected result for at least + // x64, x86 and arm64. + assertEq(true, + requiredArchs.every(a => expectedAllTargets.hasOwnProperty(a))); + + // Poke the build-configuration object to find out what target we're + // generating code for. + let conf = getBuildConfiguration(); + let genX64 = conf.x64; + let genX86 = conf.x86; + let genArm64 = conf.arm64; + let genArm = conf.arm; + // So far so good, except .. X64 or X86 might be emulating something else. + if (genX64 && genArm64 && conf['arm64-simulator']) { + genX64 = false; + } + if (genX86 && genArm && conf['arm-simulator']) { + genX86 = false; + } + + // Check we've definitively identified exactly one architecture to test. + assertEq(1, [genX64, genX86, genArm64, genArm].map(x => x ? 1 : 0) + .reduce((a,b) => a+b, 0)); + + // Decide on the arch name for which we're testing. Everything is keyed + // off this. + let archName = ""; + if (genX64) { + archName = "x64"; + } else if (genX86) { + archName = "x86"; + } else if (genArm64) { + archName = "arm64"; + } else if (genArm) { + archName = "arm"; + } + if (options.log) { + print("---- testing for architecture \"" + archName + "\""); + } + // If this fails, it means we're running on an "unknown" architecture. + assertEq(true, archName.length > 0); + + // Finalise options, by promoting arch-specific ones to the top level of + // the options object. + options = promoteArchSpecificOptions(options, archName); + + // Get the prefix and suffix strings for the target. + assertEq(true, prefixAndSuffix.hasOwnProperty(archName)); + let prefix = prefixAndSuffix[archName].prefix; + let suffix = prefixAndSuffix[archName].suffix; + assertEq(true, prefix.length >= 10); + assertEq(true, suffix.length >= 10); + + // Get the expected output string, or skip the test if no expected output + // has been provided. Note, because of the assertion near the top of this + // file, this will currently only allow arm(32) tests to be skipped. + let expected = ""; + if (expectedAllTargets.hasOwnProperty(archName)) { + expected = expectedAllTargets[archName]; + } else { + // Paranoia. Don't want to silently skip tests due to logic bugs above. + assertEq(archName, "arm"); + if (options.log) { + print("---- !! no expected output for target, skipping !!"); + } + return; + } + + // Finalise the expected-result string, and stash the original for + // debug-printing. + expectedInitial = expected; + if (!options.no_prefix) { + expected = prefix + '\n' + expected; + } + if (!options.no_suffix) { + expected = expected + '\n' + suffix; + } + if (genArm) { + // For obscure reasons, the arm(32) disassembler prints the + // instruction word twice. Rather than forcing all expected lines to + // do the same, we detect any line starting with 8 hex digits followed + // by a space, and duplicate them so as to match the + // disassembler's output. + let newExpected = ""; + let pattern = /^[0-9a-fA-F]{8} /; + for (line of expected.split(/\n+/)) { + // Remove whitespace at the start of the line. This could happen + // for continuation lines in backtick-style expected strings. + while (line.match(/^\s/)) { + line = line.slice(1); + } + if (line.match(pattern)) { + line = line.slice(0,9) + line; + } + newExpected = newExpected + line + "\n"; + } + expected = newExpected; + } + expected = fixlines(expected); + + // Compile the test case and collect disassembly output. + let ins = wasmEvalText(module_text, {}, options.features); + if (options.instanceBox) + options.instanceBox.value = ins; + let output = wasmDis(ins.exports[export_name], {tier:"ion", asString:true}); + + // Check for success, print diagnostics + let output_matches_expected = output.match(new RegExp(expected)) != null; + if (!output_matches_expected) { + print("---- adhoc-tier1-test.js: TEST FAILED ----"); + } + if (options.log && output_matches_expected) { + print("---- adhoc-tier1-test.js: TEST PASSED ----"); + } + if (options.log || !output_matches_expected) { + print("---- module text"); + print(module_text); + print("---- actual"); + print(output); + print("---- expected (initial)"); + print(expectedInitial); + print("---- expected (as used)"); + print(expected); + print("----"); + } + + // Finally, the whole point of this: + assertEq(output_matches_expected, true); +} diff --git a/js/src/jit-test/lib/andTestHelper.js b/js/src/jit-test/lib/andTestHelper.js new file mode 100644 index 0000000000..8e89a399ae --- /dev/null +++ b/js/src/jit-test/lib/andTestHelper.js @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function andTestHelper(a, b, n) +{ + var k = 0; + for (var i = 0; i < n; i++) { + if (a && b) + k += i; + } + return k; +} diff --git a/js/src/jit-test/lib/array-compare.js b/js/src/jit-test/lib/array-compare.js new file mode 100644 index 0000000000..ede737b8c3 --- /dev/null +++ b/js/src/jit-test/lib/array-compare.js @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Library file for tests to load. + +function SameValue(v1, v2) +{ + if (v1 === 0 && v2 === 0) + return 1 / v1 === 1 / v2; + if (v1 !== v1 && v2 !== v2) + return true; + return v1 === v2; +} + +function arraysEqual(a1, a2) +{ + var len1 = a1.length, len2 = a2.length; + if (len1 !== len2) + return false; + for (var i = 0; i < len1; i++) + { + if (!SameValue(a1[i], a2[i])) + return false; + } + return true; +} + diff --git a/js/src/jit-test/lib/asm.js b/js/src/jit-test/lib/asm.js new file mode 100644 index 0000000000..5a9606a8f1 --- /dev/null +++ b/js/src/jit-test/lib/asm.js @@ -0,0 +1,126 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +load(libdir + "asserts.js"); + +const USE_ASM = '"use asm";'; +const HEAP_IMPORTS = "const i8=new glob.Int8Array(b);var u8=new glob.Uint8Array(b);"+ + "const i16=new glob.Int16Array(b);var u16=new glob.Uint16Array(b);"+ + "const i32=new glob.Int32Array(b);var u32=new glob.Uint32Array(b);"+ + "const f32=new glob.Float32Array(b);var f64=new glob.Float64Array(b);"; +const BUF_MIN = 64 * 1024; +const BUF_CHANGE_MIN = 16 * 1024 * 1024; +const BUF_64KB = new ArrayBuffer(BUF_MIN); + +function asmCompile() +{ + var f = Function.apply(null, arguments); + assertEq(!isAsmJSCompilationAvailable() || isAsmJSModule(f), true); + return f; +} + +function asmCompileCached() +{ + if (!isAsmJSCompilationAvailable()) + return Function.apply(null, arguments); + + var f = Function.apply(null, arguments); + assertEq(isAsmJSModule(f), true); + return f; +} + +function assertAsmDirectiveFail(str) +{ + if (!isAsmJSCompilationAvailable()) + return; + + assertWarning(() => { + eval(str) + }, /meaningful in the Directive Prologue/); +} + +function assertAsmTypeFail() +{ + if (!isAsmJSCompilationAvailable()) + return; + + // Verify no error is thrown with warnings off + Function.apply(null, arguments); + + // Turn on throwing on validation errors + var oldOpts = options("throw_on_asmjs_validation_failure"); + assertEq(oldOpts.indexOf("throw_on_asmjs_validation_failure"), -1); + + var caught = false; + try { + Function.apply(null, arguments); + } catch (e) { + if (!e.message.includes("asm.js type error:")) + throw new Error("Didn't catch the expected type failure error; instead caught: " + e + "\nStack: " + new Error().stack); + caught = true; + } + if (!caught) + throw new Error("Didn't catch the type failure error"); + + // Turn warnings-as-errors back off + options("throw_on_asmjs_validation_failure"); +} + +function assertAsmLinkFail(f, ...args) +{ + if (!isAsmJSCompilationAvailable()) + return; + + assertEq(isAsmJSModule(f), true); + + // Verify no error is thrown with warnings off + var ret = f.apply(null, args); + + assertEq(isAsmJSFunction(ret), false); + if (typeof ret === 'object') { + for (var i in ret) { + assertEq(isAsmJSFunction(ret[i]), false); + } + } + + assertWarning(() => { + f.apply(null, args); + }, /disabled by linker/); +} + +// Linking should throw an exception even without warnings-as-errors +function assertAsmLinkAlwaysFail(f, ...args) +{ + var caught = false; + try { + f.apply(null, args); + } catch (e) { + caught = true; + } + if (!caught) + throw new Error("Didn't catch the link failure error"); +} + +function assertAsmLinkDeprecated(f, ...args) +{ + if (!isAsmJSCompilationAvailable()) + return; + + assertWarning(() => { + f.apply(null, args); + }, /asm.js type error:/) +} + +function asmLink(f, ...args) +{ + if (!isAsmJSCompilationAvailable()) + return f.apply(null, args); + + var ret; + assertNoWarning(() => { + ret = f.apply(null, args); + }, "No warning for asmLink") + + return ret; +} diff --git a/js/src/jit-test/lib/assert-offset-columns.js b/js/src/jit-test/lib/assert-offset-columns.js new file mode 100644 index 0000000000..6fb75ac74c --- /dev/null +++ b/js/src/jit-test/lib/assert-offset-columns.js @@ -0,0 +1,79 @@ +// Set breakpoints "everywhere" in a function, then call the function and check that +// the breakpoints were added are at the expected columns, and the breakpoints +// were executed in th expected order. +// +// `code` is a JS Script. The final line should define a function `f` to validate. +// `expectedBpts` is a string of spaces and carets ('^'). Throws if we don't hit +// breakpoints on exactly the columns indicated by the carets. +// `expectedOrdering` is a string of integer indices for the offsets that are +// executed, in the order that then are executed. Test code can also push +// additional items into this string using items.push("!"). +function assertOffsetColumns(code, expectedBpts, expectedOrdering = null) { + if (expectedOrdering === null) { + // The default ordering simply runs the breakpoints in order. + expectedOrdering = Array.from(expectedBpts.match(/\^/g), (_, i) => i).join(" "); + } + + // Define the function `f` in a new global. + const global = newGlobal({newCompartment: true}); + + const lines = code.split(/\r?\n|\r]/g); + const initCode = lines.slice(0, -1).join("\n"); + const execCode = lines[lines.length - 1]; + + // Treat everything but the last line as initialization code. + global.eval(initCode); + + // Run the test code itself. + global.eval(execCode); + + // Allow some tests to append to a log that will show up in expected ordering. + const hits = global.hits = []; + const bpts = new Set(); + + // Set breakpoints everywhere and call the function. + const dbg = new Debugger; + let debuggeeFn = dbg.addDebuggee(global).makeDebuggeeValue(global.f); + if (debuggeeFn.isBoundFunction) { + debuggeeFn = debuggeeFn.boundTargetFunction; + } + + const { script } = debuggeeFn; + for (const offset of script.getAllColumnOffsets()) { + assertEq(offset.lineNumber, 1); + assertEq(offset.columnNumber < execCode.length, true); + bpts.add(offset.columnNumber); + + script.setBreakpoint(offset.offset, { + hit(frame) { + hits.push(offset.columnNumber); + }, + }); + } + global.f(3); + + const actualBpts = Array.from(execCode, (_, i) => { + return bpts.has(i) ? "^" : " "; + }).join(""); + + if (actualBpts.trimEnd() !== expectedBpts.trimEnd()) { + throw new Error(`Assertion failed: + code: ${execCode} + expected bpts: ${expectedBpts} + actual bpts: ${actualBpts}\n`); + } + + const indexLookup = new Map( + Array.from(bpts).sort().map((col, i) => [col, i])); + const actualOrdering = hits + .map(item => typeof item === "number" ? indexLookup.get(item) : item) + .join(" "); + + if (actualOrdering.trimEnd() !== expectedOrdering.trimEnd()) { + throw new Error(`Assertion failed: + code: ${execCode} + bpts: ${expectedBpts} + expected order: ${expectedOrdering} + actual order: ${actualOrdering}\n`); + } +} diff --git a/js/src/jit-test/lib/asserts.js b/js/src/jit-test/lib/asserts.js new file mode 100644 index 0000000000..f0b3fe3bff --- /dev/null +++ b/js/src/jit-test/lib/asserts.js @@ -0,0 +1,87 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +load(libdir + "non262.js"); + +if (typeof assertWarning === 'undefined') { + var assertWarning = function assertWarning(f, pattern) { + enableLastWarning(); + + // Verify that a warning is issued. + clearLastWarning(); + f(); + var warning = getLastWarning(); + clearLastWarning(); + + disableLastWarning(); + + if (warning) { + if (!warning.message.match(pattern)) { + throw new Error(`assertWarning failed: "${warning.message}" does not match "${pattern}"`); + } + return; + } + + throw new Error("assertWarning failed: no warning"); + }; +} + +if (typeof assertNoWarning === 'undefined') { + var assertNoWarning = function assertNoWarning(f, msg) { + enableLastWarning(); + + // Verify that no warning is issued. + clearLastWarning(); + f(); + var warning = getLastWarning(); + clearLastWarning(); + + disableLastWarning(); + + if (warning) { + if (msg) { + print("assertNoWarning: " + msg); + } + + throw Error("assertNoWarning: Unexpected warning when calling: " + f); + } + }; +} + +if (typeof assertErrorMessage === 'undefined') { + var assertErrorMessage = function assertErrorMessage(f, ctor, test) { + try { + f(); + } catch (e) { + // Propagate non-specific OOM errors, we never test for these with + // assertErrorMessage, as there is no meaningful ctor. + if (e === "out of memory") + throw e; + if (!(e instanceof ctor)) + throw new Error("Assertion failed: expected exception " + ctor.name + ", got " + e); + if (typeof test == "string") { + if (test != e.message) + throw new Error("Assertion failed: expected " + test + ", got " + e.message); + } else { + if (!test.test(e.message)) + throw new Error("Assertion failed: expected " + test.toString() + ", got " + e.message); + } + return; + } + throw new Error("Assertion failed: expected exception " + ctor.name + ", no exception thrown"); + }; +} + +if (typeof assertTypeErrorMessage === 'undefined') { + var assertTypeErrorMessage = function assertTypeErrorMessage(f, test) { + assertErrorMessage(f, TypeError, test); + }; +} + +if (typeof assertRangeErrorMessage === 'undefined') { + var assertRangeErrorMessage = function assertRangeErrorMessage(f, test) { + assertErrorMessage(f, RangeError, test); + }; +} diff --git a/js/src/jit-test/lib/bytecode-cache.js b/js/src/jit-test/lib/bytecode-cache.js new file mode 100644 index 0000000000..1f8f58643a --- /dev/null +++ b/js/src/jit-test/lib/bytecode-cache.js @@ -0,0 +1,55 @@ + +function evalWithCache(code, ctx) { + ctx = ctx || {}; + ctx = Object.create(ctx, { + fileName: { value: "evalWithCacheCode.js" }, + lineNumber: { value: 0 } + }); + code = code instanceof Object ? code : cacheEntry(code); + + var incremental = ctx.incremental || false; + + // We create a new global ... + if (!("global" in ctx)) + ctx.global = newGlobal({newCompartment: ctx.newCompartment}); + + var ctx_save = Object.create(ctx, { + saveIncrementalBytecode: { value: true } + }); + + // Fetch the verification function from the evaluation context. This function + // is used to assert the state of the script/function after each run of the + // evaluate function. + var checkAfter = ctx.checkAfter || function(ctx) {}; + + // The generation counter is used to represent environment variations which + // might cause the program to run differently, and thus to have a different + // set of functions executed. + ctx.global.generation = 0; + var res1 = evaluate(code, ctx_save); + checkAfter(ctx); + + ctx.global.generation = 1; + var res2 = evaluate(code, Object.create(ctx_save, {loadBytecode: { value: true } })); + checkAfter(ctx); + + ctx.global.generation = 2; + var res3 = evaluate(code, Object.create(ctx, {loadBytecode: { value: true } })); + checkAfter(ctx); + + ctx.global.generation = 3; + var res0 = evaluate(code, ctx); + checkAfter(ctx); + + if (ctx.assertEqResult) { + assertEq(res0, res1); + assertEq(res0, res2); + assertEq(res0, res3); + } + + if (ctx.checkFrozen) { + assertEq(Object.isFrozen(res0), Object.isFrozen(res1)); + assertEq(Object.isFrozen(res0), Object.isFrozen(res2)); + assertEq(Object.isFrozen(res0), Object.isFrozen(res3)); + } +} diff --git a/js/src/jit-test/lib/census.js b/js/src/jit-test/lib/census.js new file mode 100644 index 0000000000..79de8d0060 --- /dev/null +++ b/js/src/jit-test/lib/census.js @@ -0,0 +1,161 @@ +// Functions for checking results returned by Debugger.Memory.prototype.takeCensus. + +const Census = {}; + +(function () { + + // Census.walkCensus(subject, name, walker[, ignore]) + // + // Use |walker| to check |subject|, a census object of the sort returned by + // Debugger.Memory.prototype.takeCensus: a tree of objects with integers at the + // leaves. Use |name| as the name for |subject| in diagnostic messages. Return + // the number of leaves of |subject| we visited. + // + // A walker is an object with three methods: + // + // - enter(prop): Return the walker we should use to check the property of the + // subject census named |prop|. This is for recursing into the subobjects of + // the subject. + // + // - done(ignore): Called after we have called 'enter' on every property of + // the subject. Passed the |ignore| set of properties. + // + // - check(value): Check |value|, a leaf in the subject. + // + // Walker methods are expected to simply throw if a node we visit doesn't look + // right. + // + // The optional |ignore| parameter allows you to specify a |Set| of property + // names which should be ignored. The walker will not traverse such + // properties. + Census.walkCensus = (subject, name, walker, ignore = new Set()) => + walk(subject, name, walker, ignore, 0); + + function walk(subject, name, walker, ignore, count) { + if (typeof subject === 'object') { + print(name); + for (let prop in subject) { + if (ignore.has(prop)) { + continue; + } + count = walk(subject[prop], + name + "[" + JSON.stringify(prop) + "]", + walker.enter(prop), + ignore, + count); + } + walker.done(ignore); + } else { + print(name + " = " + JSON.stringify(subject)); + walker.check(subject); + count++; + } + + return count; + } + + // A walker that doesn't check anything. + Census.walkAnything = { + enter: () => Census.walkAnything, + done: () => undefined, + check: () => undefined + }; + + // A walker that requires all leaves to be zeros. + Census.assertAllZeros = { + enter: () => Census.assertAllZeros, + done: () => undefined, + check: elt => assertEq(elt, 0) + }; + + function expectedObject() { + throw "Census mismatch: subject has leaf where basis has nested object"; + } + + function expectedLeaf() { + throw "Census mismatch: subject has nested object where basis has leaf"; + } + + // Return a function that, given a 'basis' census, returns a census walker that + // compares the subject census against the basis. The returned walker calls the + // given |compare|, |missing|, and |extra| functions as follows: + // + // - compare(subjectLeaf, basisLeaf): Check a leaf of the subject against the + // corresponding leaf of the basis. + // + // - missing(prop, value): Called when the subject is missing a property named + // |prop| which is present in the basis with value |value|. + // + // - extra(prop): Called when the subject has a property named |prop|, but the + // basis has no such property. This should return a walker that can check + // the subject's value. + function makeBasisChecker({compare, missing, extra}) { + return function makeWalker(basis) { + if (typeof basis === 'object') { + var unvisited = new Set(Object.getOwnPropertyNames(basis)); + return { + enter: prop => { + unvisited.delete(prop); + if (prop in basis) { + return makeWalker(basis[prop]); + } else { + return extra(prop); + } + }, + + done: ignore => [...unvisited].filter(p => !ignore.has(p)).forEach(p => missing(p, basis[p])), + check: expectedObject + }; + } else { + return { + enter: expectedLeaf, + done: expectedLeaf, + check: elt => compare(elt, basis) + }; + } + }; + } + + function missingProp(prop) { + throw "Census mismatch: subject lacks property present in basis: " + JSON.stringify(prop); + } + + function extraProp(prop) { + throw "Census mismatch: subject has property not present in basis: " + JSON.stringify(prop); + } + + // Return a walker that checks that the subject census has counts all equal to + // |basis|. + Census.assertAllEqual = makeBasisChecker({ + compare: assertEq, + missing: missingProp, + extra: extraProp + }); + + // Return a walker that checks that the subject census has at least as many + // items of each category as |basis|. + Census.assertAllNotLessThan = makeBasisChecker({ + compare: (subject, basis) => assertEq(subject >= basis, true), + missing: missingProp, + extra: () => Census.walkAnything + }); + + // Return a walker that checks that the subject census has at most as many + // items of each category as |basis|. + Census.assertAllNotMoreThan = makeBasisChecker({ + compare: (subject, basis) => assertEq(subject <= basis, true), + missing: missingProp, + extra: () => Census.walkAnything + }); + + // Return a walker that checks that the subject census has within |fudge| + // items of each category of the count in |basis|. + Census.assertAllWithin = function (fudge, basis) { + return makeBasisChecker({ + compare: (subject, basis) => assertEq(Math.abs(subject - basis) <= fudge, true), + missing: missingProp, + extra: () => Census.walkAnything + })(basis); + } + +})(); diff --git a/js/src/jit-test/lib/codegen-arm64-test.js b/js/src/jit-test/lib/codegen-arm64-test.js new file mode 100644 index 0000000000..542f34f826 --- /dev/null +++ b/js/src/jit-test/lib/codegen-arm64-test.js @@ -0,0 +1,40 @@ +// Scaffolding for testing arm64 Ion code generation patterns . See +// codegen-x64-test.js in this directory for more information. + +load(libdir + "codegen-test-common.js"); + +// End of prologue +var arm64_prefix = ` +910003fd mov x29, sp +910003fc mov x28, sp +`; + +// Start of epilogue +var arm64_suffix = ` +f94003fd ldr x29, \\[sp\\] +`; + +// For when nothing else applies: `module_text` is the complete source text of +// the module, `export_name` is the name of the function to be tested, +// `expected` is the non-preprocessed pattern, and options is an options bag, +// described above. +function codegenTestARM64_adhoc(module_text, export_name, expected, options = {}) { + assertEq(hasDisassembler(), true); + + let ins = wasmEvalText(module_text, {}, options.features); + if (options.instanceBox) + options.instanceBox.value = ins; + let output = wasmDis(ins.exports[export_name], {tier:"ion", asString:true}); + if (!options.no_prefix) + expected = arm64_prefix + '\n' + expected; + if (!options.no_suffix) + expected = expected + '\n' + arm64_suffix; + expected = fixlines(expected); + if (options.log) { + print(module_text); + print(output); + print(expected); + } + assertEq(output.match(new RegExp(expected)) != null, true); +} + diff --git a/js/src/jit-test/lib/codegen-test-common.js b/js/src/jit-test/lib/codegen-test-common.js new file mode 100644 index 0000000000..ad5511746b --- /dev/null +++ b/js/src/jit-test/lib/codegen-test-common.js @@ -0,0 +1,53 @@ +// Set to true to emit ' +' instead of the unreadable '\s+'. +var SPACEDEBUG = false; + +// Any hex string +var HEX = '[0-9a-fA-F]' +var HEXES = `${HEX}+`; + +function wrap(options, funcs) { + if ('memory' in options) + return `(module (memory ${options.memory}) ${funcs})`; + return `(module ${funcs})`; +} + +function fixlines(s) { + return s.split(/\n+/) + .map(strip) + .filter(x => x.length > 0) + .map(x => '(?:0x)?' + HEXES + ' ' + x) + .map(spaces) + .join('\n'); +} + +function strip(s) { + while (s.length > 0 && isspace(s.charAt(0))) + s = s.substring(1); + while (s.length > 0 && isspace(s.charAt(s.length-1))) + s = s.substring(0, s.length-1); + return s; +} + +function striplines(s) { + return s.split('\n').map(strip).join('\n'); +} + +function spaces(s) { + let t = ''; + let i = 0; + while (i < s.length) { + if (isspace(s.charAt(i))) { + t += SPACEDEBUG ? ' +' : '\\s+'; + i++; + while (i < s.length && isspace(s.charAt(i))) + i++; + } else { + t += s.charAt(i++); + } + } + return t; +} + +function isspace(c) { + return c == ' ' || c == '\t'; +} diff --git a/js/src/jit-test/lib/codegen-x64-test.js b/js/src/jit-test/lib/codegen-x64-test.js new file mode 100644 index 0000000000..5d64226bf2 --- /dev/null +++ b/js/src/jit-test/lib/codegen-x64-test.js @@ -0,0 +1,184 @@ +// Scaffolding for testing x64 Ion code generation patterns). See +// ../tests/wasm/README-codegen.md for general information, and the *codegen.js +// tests in that directory and its subdirectories for specifics. +// +// The structure of the inputs can vary for the different testing predicates but +// each case generally has an operator name and an expected-pattern; the latter +// is a string that represents a regular expression, possibly with newlines, +// representing the instruction or instructions we are looking for for the +// operator. Spaces in the expected-pattern are arbitrary, we preprocess the +// pattern to replace any space string with \s+. Lines are separated by +// newlines and leading and trailing spaces are currently stripped. +// +// The testers additionally take an optional options bag with the following +// optional entries: +// features: if present, an object to pass as the last argument to functions +// that compile wasm bytecode +// instanceBox: if present, an object with a `value` property that will +// receive the constructed instance +// no_prefix: by default, the required pattern must be immediately preceded +// by `x64_prefix`, and this is checked. Setting this to true skips +// the check. +// no_suffix: by default, the required pattern must be immediately followed +// by `x64_suffix`, and this is checked. Setting this to true skips +// the check. +// memory: if present, add a memory of length given by this property +// log: for debugging -- print the disassembly, then the preprocessed pattern + +load(libdir + "codegen-test-common.js"); + +// RIP-relative address following the instruction mnemonic +var RIPR = `0x${HEXES}`; + +// RIP-relative address in the binary encoding +var RIPRADDR = `${HEX}{2} ${HEX}{2} ${HEX}{2} ${HEX}{2}`; + +// End of prologue +var x64_prefix = `48 89 e5 mov %rsp, %rbp` + +// Start of epilogue +var x64_suffix = `5d pop %rbp`; + +// v128 OP v128 -> v128 +// inputs: [[complete-opname, expected-pattern], ...] +function codegenTestX64_v128xv128_v128(inputs, options = {}) { + for ( let [op, expected] of inputs ) { + codegenTestX64BinopInternal(op, expected, options, 'v128', 'v128', 'v128', '0', '1'); + } +} + +// v128 OP param1-type -> v128 +// inputs: [[complete-opname, param1-type, expected-pattern], ...] +function codegenTestX64_v128xPTYPE_v128(inputs, options = {}) { + for ( let [op, p1type, expected] of inputs ) { + codegenTestX64BinopInternal(op, expected, options, 'v128', p1type, 'v128', '0', '1'); + } +} + +// v128 OP literal -> v128 +// inputs: [[complete-opname, rhs-literal, expected-pattern], ...] +function codegenTestX64_v128xLITERAL_v128(inputs, options = {}) { + for ( let [op, literal, expected] of inputs ) { + codegenTestX64_adhoc(wrap(options, ` + (func (export "f") (param v128) (result v128) + (${op} (local.get 0) ${literal}))`), + 'f', + expected, + options) + } +} + +// literal OP v128 -> v128 +// inputs: [[complete-opname, lhs-literal, expected-pattern], ...] +function codegenTestX64_LITERALxv128_v128(inputs, options = {}) { + for ( let [op, literal, expected] of inputs ) { + codegenTestX64_adhoc(wrap(options, ` + (func (export "f") (param v128) (result v128) + (${op} ${literal} (local.get 0)))`), + 'f', + expected, + options) + } +} + +// v128 OP v128 -> v128, but operands are swapped +// inputs: [[complete-opname, expected-pattern], ...] +function codegenTestX64_v128xv128_v128_reversed(inputs, options = {}) { + for ( let [op, expected] of inputs ) { + codegenTestX64BinopInternal(op, expected, options, 'v128', 'v128', 'v128', '1', '0'); + } +} + +// OP v128 -> v128 +// inputs: [[complete-opname, expected-pattern], ...] +function codegenTestX64_v128_v128(inputs, options = {}) { + for ( let [op, expected] of inputs ) { + codegenTestX64_adhoc(wrap(options, ` + (func (export "f") (param v128) (result v128) + (${op} (local.get 0)))`), + 'f', + expected, + options); + } +} + +// OP param-type -> v128 +// inputs [[complete-opname, param-type, expected-pattern], ...] +function codegenTestX64_PTYPE_v128(inputs, options = {}) { + for ( let [op, ptype, expected] of inputs ) { + codegenTestX64_adhoc(wrap(options, ` + (func (export "f") (param ${ptype}) (result v128) + (${op} (local.get 0)))`), + 'f', + expected, + options); + } +} + +// OP v128 -> v128, but the function takes two arguments and the first is ignored +// inputs: [[complete-opname, expected-pattern], ...] +function codegenTestX64_IGNOREDxv128_v128(inputs, options = {}) { + for ( let [op, expected] of inputs ) { + codegenTestX64_adhoc(wrap(options, ` + (func (export "f") (param v128) (param v128) (result v128) + (${op} (local.get 1)))`), + 'f', + expected, + options); + } +} + +// () -> v128 +// inputs: [[complete-opname, expected-pattern], ...] +function codegenTestX64_unit_v128(inputs, options = {}) { + for ( let [op, expected] of inputs ) { + codegenTestX64_adhoc(wrap(options, ` + (func (export "f") (result v128) + (${op}))`), + 'f', + expected, + options); + } +} + +// For when nothing else applies: `module_text` is the complete source text of +// the module, `export_name` is the name of the function to be tested, +// `expected` is the non-preprocessed pattern, and options is an options bag, +// described above. +function codegenTestX64_adhoc(module_text, export_name, expected, options = {}) { + assertEq(hasDisassembler(), true); + + let ins = wasmEvalText(module_text, {}, options.features); + if (options.instanceBox) + options.instanceBox.value = ins; + let output = wasmDis(ins.exports[export_name], {tier:"ion", asString:true}); + if (!options.no_prefix) + expected = x64_prefix + '\n' + expected; + if (!options.no_suffix) + expected = expected + '\n' + x64_suffix; + const expected_pretty = striplines(expected); + expected = fixlines(expected); + + const success = output.match(new RegExp(expected)) != null; + if (options.log || !success) { + print("Module text:") + print(module_text); + print("Actual output:") + print(output); + print("Expected output (easy-to-read and fully-regex'd):") + print(expected_pretty); + print(expected); + } + assertEq(success, true); +} + +// Internal code below this line + +function codegenTestX64BinopInternal(op, expected, options, p0type, p1type, restype, arg0, arg1) { + codegenTestX64_adhoc(wrap(options, ` + (func (export "f") (param ${p0type}) (param ${p1type}) (result ${restype}) + (${op} (local.get ${arg0}) (local.get ${arg1})))`), + 'f', + expected, + options); +} diff --git a/js/src/jit-test/lib/codegen-x86-test.js b/js/src/jit-test/lib/codegen-x86-test.js new file mode 100644 index 0000000000..edf3e916fb --- /dev/null +++ b/js/src/jit-test/lib/codegen-x86-test.js @@ -0,0 +1,84 @@ +// Scaffolding for testing x86 Ion code generation patterns . See +// codegen-x64-test.js in this directory for more information. + +load(libdir + "codegen-test-common.js"); + +// Note that Zydis disassembles x86 absolute addresses as relative, so +// the binary encoding and the text encoding may not correspond precisely. + +// Absolute address (disp32) following the instruction mnemonic. +var ABS = `0x${HEXES}`; + +// Absolute address (disp32) in the binary encoding. +var ABSADDR = `${HEX}{2} ${HEX}{2} ${HEX}{2} ${HEX}{2}`; + +// End of prologue. The mov to eax is debug code, inserted by the register +// allocator to clobber eax before a move group. But it is only present if +// there is a move group there. +// +// -0x21524111 is 0xDEADBEEF. +var x86_prefix = ` +8b ec mov %esp, %ebp( +b8 ef be ad de mov \\$-0x21524111, %eax)? +` + +// `.bp` because zydis chooses 'rbp' even on 32-bit systems +var x86_loadarg0 = ` +f3 0f 6f 45 ${HEX}{2} movdqux 0x${HEXES}\\(%.bp\\), %xmm0 +`; + +// Start of epilogue. `.bp` for the same reason as above. +var x86_suffix = `5d pop %.bp`; + +// v128 OP literal -> v128 +// inputs: [[complete-opname, rhs-literal, expected-pattern], ...] +function codegenTestX86_v128xLITERAL_v128(inputs, options = {}) { + for ( let [op, literal, expected] of inputs ) { + codegenTestX86_adhoc(wrap(options, ` + (func (export "f") (param v128) (result v128) + (${op} (local.get 0) ${literal}))`), + 'f', + x86_loadarg0 + expected, + options) + } +} + +// For when nothing else applies: `module_text` is the complete source text of +// the module, `export_name` is the name of the function to be tested, +// `expected` is the non-preprocessed pattern, and options is an options bag, +// described above. +function codegenTestX86_adhoc(module_text, export_name, expected, options = {}) { + assertEq(hasDisassembler(), true); + + let ins = wasmEvalText(module_text); + let output = wasmDis(ins.exports[export_name], {tier:"ion", asString:true}); + + const expected_initial = expected; + if (!options.no_prefix) + expected = x86_prefix + '\n' + expected; + if (!options.no_suffix) + expected = expected + '\n' + x86_suffix; + expected = fixlines(expected); + + const output_matches_expected = output.match(new RegExp(expected)) != null; + if (!output_matches_expected) { + print("---- codegen-x86-test.js: TEST FAILED ----"); + } + if (options.log && output_matches_expected) { + print("---- codegen-x86-test.js: TEST PASSED ----"); + } + if (options.log || !output_matches_expected) { + print("---- module text"); + print(module_text); + print("---- actual"); + print(output); + print("---- expected (initial)"); + print(expected_initial); + print("---- expected (as used)"); + print(expected); + print("----"); + } + + assertEq(output_matches_expected, true); +} + diff --git a/js/src/jit-test/lib/dataview.js b/js/src/jit-test/lib/dataview.js new file mode 100644 index 0000000000..68228f324b --- /dev/null +++ b/js/src/jit-test/lib/dataview.js @@ -0,0 +1,82 @@ +function typeName(typedArrayCtor) { + return typedArrayCtor.name.slice(0, -"Array".length); +} + +const nativeIsLittleEndian = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1; + +function toEndianess(type, v, littleEndian) { + // Disable Ion compilation to call the native, non-inlined DataView functions. + with ({}); // no-ion + assertEq(inIon() !== true, true); + + let dv = new DataView(new ArrayBuffer(type.BYTES_PER_ELEMENT)); + + let name = typeName(type); + dv[`set${name}`](0, v, nativeIsLittleEndian); + return dv[`get${name}`](0, littleEndian); +} + +function toLittleEndian(type, v) { + return toEndianess(type, v, true); +} + +function toBigEndian(type, v) { + return toEndianess(type, v, false); +} + +// Shared test data for various DataView tests. +function createTestData() { + const tests = [ + { + type: Int8Array, + values: [-128, -127, -2, -1, 0, 1, 2, 126, 127], + }, + { + type: Uint8Array, + values: [0, 1, 2, 126, 127, 128, 254, 255], + }, + { + type: Int16Array, + values: [-32768, -32767, -2, -1, 0, 1, 2, 32766, 32767], + }, + { + type: Uint16Array, + values: [0, 1, 2, 32766, 32767, 32768, 65534, 65535], + }, + { + type: Int32Array, + values: [-2147483648, -2147483647, -2, -1, 0, 1, 2, 2147483646, 2147483647], + }, + { + type: Uint32Array, + values: [0, 1, 2, 2147483646, 2147483647], // Representable as Int32 + }, + { + type: Uint32Array, + values: [0, 1, 2, 2147483646, 2147483647, 2147483648, 4294967294, 4294967295], + }, + { + type: Float32Array, + values: [-NaN, -Infinity, -0.5, -0, +0, 0.5, Infinity, NaN], + }, + { + type: Float64Array, + values: [-NaN, -Infinity, -0.5, -0, +0, 0.5, Infinity, NaN], + }, + { + type: BigInt64Array, + values: [-9223372036854775808n, -9223372036854775807n, -2n, -1n, 0n, 1n, 2n, 9223372036854775806n, 9223372036854775807n], + }, + { + type: BigUint64Array, + values: [0n, 1n, 2n, 9223372036854775806n, 9223372036854775807n, 9223372036854775808n, 18446744073709551614n, 18446744073709551615n], + }, + ]; + + tests.forEach(data => { + data.littleEndian = data.values.map(v => toLittleEndian(data.type, v)); + data.bigEndian = data.values.map(v => toBigEndian(data.type, v)); + }); + + return tests; +} diff --git a/js/src/jit-test/lib/debuggerNXHelper.js b/js/src/jit-test/lib/debuggerNXHelper.js new file mode 100644 index 0000000000..61792da84d --- /dev/null +++ b/js/src/jit-test/lib/debuggerNXHelper.js @@ -0,0 +1,90 @@ +function testDebuggerHooksNX(dbg, g, testHook) { + function testDebuggerHook(hookName, trigger) { + var hit = false; + dbg[hookName] = () => { + hit = true; + dbg[hookName] = undefined; + testHook(hookName); + }; + trigger(); + assertEq(hit, true); + } + + // Hooks on the Debugger instance itself. + testDebuggerHook("onDebuggerStatement", + () => { g.eval("debugger;"); }); + + testDebuggerHook("onExceptionUnwind", + () => { + try { + g.eval("throw 42;"); + } catch (e) { + assertEq(e, 42); + } + }); + + testDebuggerHook("onNewScript", + () => { g.eval("42"); }); + + testDebuggerHook("onEnterFrame", + () => { g.eval("(() => {})()"); }); + + testDebuggerHook("onNewGlobalObject", + () => { newGlobal(); }); + + if ('Promise' in g) { + testDebuggerHook("onNewPromise", + () => { new g.Promise(()=>{}); }); + + testDebuggerHook("onPromiseSettled", + () => { + var p = new g.Promise(()=>{}); + g.settlePromiseNow(p); + }); + } + + // Hooks on frames. + var onStepHit = false; + var onPopHit = false; + dbg.onEnterFrame = (frame) => { + dbg.onEnterFrame = undefined; + + frame.onStep = () => { + onStepHit = true; + frame.onStep = undefined; + testHook("onStep"); + }; + + frame.onPop = () => { + onPopHit = true; + testHook("onPop"); + }; + }; + g.eval("42"); + assertEq(onStepHit, true); + + // We can't actually assert that onPop executed because there's one test + // that tests NX works across a removeDebuggee/addDebuggee toggle. The + // removeDebuggee purges D.F instances, so the onPop won't be called even + // when the global is re-added. + //assertEq(onPopHit, true); + + // Breakpoints + var breakpointHits = 0; + dbg.onDebuggerStatement = (frame) => { + dbg.onDebuggerStatement = undefined; + var line0 = frame.script.getOffsetLocation(frame.offset).lineNumber; + var offs = frame.script.getLineOffsets(line0 + 1); + for (let i = 0; i < offs.length; i++) { + frame.script.setBreakpoint(offs[i], { + hit: () => { + breakpointHits++; + testHook("breakpoint"); + } + }); + } + }; + g.eval(`debugger; + s = 'a'`); + assertEq(breakpointHits >= 1, true); +} diff --git a/js/src/jit-test/lib/eqArrayHelper.js b/js/src/jit-test/lib/eqArrayHelper.js new file mode 100644 index 0000000000..389d3faa0a --- /dev/null +++ b/js/src/jit-test/lib/eqArrayHelper.js @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function assertEqArray(actual, expected) { + if (actual.length != expected.length) { + throw new Error( + "array lengths not equal: got " + + JSON.stringify(actual) + ", expected " + JSON.stringify(expected)); + } + + for (var i = 0; i < actual.length; ++i) { + if (actual[i] != expected[i]) { + throw new Error( + "arrays not equal at element " + i + ": got " + + JSON.stringify(actual) + ", expected " + JSON.stringify(expected)); + } + } +} + + diff --git a/js/src/jit-test/lib/evalInFrame.js b/js/src/jit-test/lib/evalInFrame.js new file mode 100644 index 0000000000..d429b3fb6a --- /dev/null +++ b/js/src/jit-test/lib/evalInFrame.js @@ -0,0 +1,32 @@ +var evalInFrame = (function (global) { + var dbgGlobal = newGlobal({newCompartment: true}); + var dbg = new dbgGlobal.Debugger(); + + return function evalInFrame(upCount, code) { + dbg.addDebuggee(global); + + // Skip ourself. + var frame = dbg.getNewestFrame().older; + for (var i = 0; i < upCount; i++) { + if (!frame.older) + break; + frame = frame.older; + } + + var completion = frame.eval(code); + if (completion.return) { + var v = completion.return; + if (typeof v === "object") + v = v.unsafeDereference(); + return v; + } + if (completion.throw) { + var v = completion.throw; + if (typeof v === "object") + v = v.unsafeDereference(); + throw v; + } + if (completion === null) + terminate(); + }; +})(this); diff --git a/js/src/jit-test/lib/immutable-prototype.js b/js/src/jit-test/lib/immutable-prototype.js new file mode 100644 index 0000000000..75c1f4e3ca --- /dev/null +++ b/js/src/jit-test/lib/immutable-prototype.js @@ -0,0 +1,4 @@ +function globalPrototypeChainIsMutable() +{ + return false; +} diff --git a/js/src/jit-test/lib/iteration.js b/js/src/jit-test/lib/iteration.js new file mode 100644 index 0000000000..b679014f7d --- /dev/null +++ b/js/src/jit-test/lib/iteration.js @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +load(libdir + "asserts.js"); + +if (typeof assertIteratorResult === 'undefined') { + var assertIteratorResult = function assertIteratorResult(result, value, done) { + assertEq(typeof result, "object"); + var expectedProps = ['done', 'value']; + var actualProps = Object.getOwnPropertyNames(result); + actualProps.sort(), expectedProps.sort(); + assertDeepEq(actualProps, expectedProps); + assertDeepEq(result.value, value); + assertDeepEq(result.done, done); + } +} + +if (typeof assertIteratorNext === 'undefined') { + var assertIteratorNext = function assertIteratorNext(iter, value) { + assertIteratorResult(iter.next(), value, false); + } +} + +if (typeof assertIteratorDone === 'undefined') { + var assertIteratorDone = function assertIteratorDone(iter, value) { + assertIteratorResult(iter.next(), value, true); + } +} diff --git a/js/src/jit-test/lib/jitopts.js b/js/src/jit-test/lib/jitopts.js new file mode 100644 index 0000000000..a66c02474b --- /dev/null +++ b/js/src/jit-test/lib/jitopts.js @@ -0,0 +1,70 @@ +// These predicates are for tests that require a particular set of JIT options. + +// Check if toggles match. Useful for tests that shouldn't be run if a +// different set of JIT toggles are set, since TBPL runs each jit-test +// multiple times with a variety of flags. +function jitTogglesMatch(opts) { + var currentOpts = getJitCompilerOptions(); + for (var k in opts) { + if (k.indexOf(".enable") > 0 && opts[k] != currentOpts[k]) + return false; + } + return true; +} + +// Run fn under a particular set of JIT options. +function withJitOptions(opts, fn) { + var oldOpts = getJitCompilerOptions(); + for (var k in opts) + setJitCompilerOption(k, opts[k]); + try { + fn(); + } finally { + for (var k in oldOpts) + setJitCompilerOption(k, oldOpts[k]); + } +} + +// N.B. Ion opts *must come before* baseline opts because there's some kind of +// "undo eager compilation" logic. If we don't set the baseline warmup-counter +// *after* the Ion warmup-counter we end up setting the baseline warmup-counter +// to be the default if we hit the "undo eager compilation" logic. +var Opts_BaselineEager = + { + 'ion.enable': 1, + 'ion.warmup.trigger': 100, + 'baseline.enable': 1, + 'baseline.warmup.trigger': 0, + 'offthread-compilation.enable': 1 + }; + +// Checking for offthread compilation being off is often helpful if the test +// requires a function be Ion compiled. Each individual test will usually +// finish before the Ion compilation thread has a chance to attach the +// compiled code. +var Opts_IonEagerNoOffthreadCompilation = + { + 'ion.enable': 1, + 'ion.warmup.trigger': 0, + 'baseline.enable': 1, + 'baseline.warmup.trigger': 0, + 'offthread-compilation.enable': 0, + }; + +var Opts_Ion2NoOffthreadCompilation = + { + 'ion.enable': 1, + 'ion.warmup.trigger': 3, + 'baseline.enable': 1, + 'baseline.warmup.trigger': 1, + 'offthread-compilation.enable': 0 + }; + +var Opts_NoJits = + { + 'ion.enable': 0, + 'ion.warmup.trigger': 0, + 'baseline.warmup.trigger': 0, + 'baseline.enable': 0, + 'offthread-compilation.enable': 0 + }; diff --git a/js/src/jit-test/lib/match-debugger.js b/js/src/jit-test/lib/match-debugger.js new file mode 100644 index 0000000000..19331a11fe --- /dev/null +++ b/js/src/jit-test/lib/match-debugger.js @@ -0,0 +1,64 @@ +// Debugger-oriented Pattern subclasses. + +if (typeof Match !== 'function') { + load(libdir + 'match.js'); +} + +class DebuggerObjectPattern extends Match.Pattern { + constructor(className, props) { + super(); + this.className = className; + if (props) { + this.props = Match.Pattern.OBJECT_WITH_EXACTLY(props); + } + } + + match(actual) { + if (!(actual instanceof Debugger.Object)) { + throw new Match.MatchError(`Expected Debugger.Object, got ${actual}`); + } + + if (actual.class !== this.className) { + throw new Match.MatchError(`Expected Debugger.Object of class ${this.className}, got Debugger.Object of class ${actual.class}`); + } + + if (this.props !== undefined) { + const lifted = {}; + for (const name of actual.getOwnPropertyNames()) { + const desc = actual.getOwnPropertyDescriptor(name); + if (!('value' in desc)) { + throw new Match.MatchError(`Debugger.Object referent has non-value property ${JSON.stringify(name)}`); + } + lifted[name] = desc.value; + } + + try { + this.props.match(lifted); + } catch (inner) { + if (!(inner instanceof Match.MatchError)) { + throw inner; + } + inner.message = `matching Debugger.Object referent properties:\n${inner.message}`; + throw inner; + } + } + + return true; + } +} + +// The Debugger API guarantees that various sorts of meta-objects are 1:1 with +// their referents, so it's often useful to check that two objects are === in +// patterns. +class DebuggerIdentical extends Match.Pattern { + constructor(expected) { + super(); + this.expected = expected; + } + + match(actual) { + if (actual !== this.expected) { + throw new Pattern.MatchError(`Expected exact value ${uneval(this.expected)}, got ${uneval(actual)}`); + } + } +} diff --git a/js/src/jit-test/lib/match.js b/js/src/jit-test/lib/match.js new file mode 100644 index 0000000000..70cd15a5b5 --- /dev/null +++ b/js/src/jit-test/lib/match.js @@ -0,0 +1 @@ +loadRelativeToScript("../../tests/non262/reflect-parse/Match.js"); diff --git a/js/src/jit-test/lib/math.js b/js/src/jit-test/lib/math.js new file mode 100644 index 0000000000..116ed477d8 --- /dev/null +++ b/js/src/jit-test/lib/math.js @@ -0,0 +1 @@ +loadRelativeToScript("../../tests/non262/Math/shell.js"); diff --git a/js/src/jit-test/lib/nightly-only.js b/js/src/jit-test/lib/nightly-only.js new file mode 100644 index 0000000000..471a2d7431 --- /dev/null +++ b/js/src/jit-test/lib/nightly-only.js @@ -0,0 +1,23 @@ +// Some experimental features are enabled only on nightly builds, and disabled +// on beta and release. Tests for these features should not simply disable +// themselves on all but nightly builds, because if we neglect to update such +// tests once the features cease to be experimental, we'll silently skip the +// tests on beta and release, even though they should run. + +// Call the function f. On beta and release, expect it to throw an error that is +// an instance of error. +function nightlyOnly(error, f) { + if (getBuildConfiguration().release_or_beta) { + try { + f(); + throw new Error("use of feature expected to fail on release and beta, but succeeded; please update test"); + } catch (e) { + if (!(e instanceof error)) { + throw e; + } + // All is well. + } + } else { + f(); + } +} diff --git a/js/src/jit-test/lib/non262.js b/js/src/jit-test/lib/non262.js new file mode 100644 index 0000000000..e840038a7e --- /dev/null +++ b/js/src/jit-test/lib/non262.js @@ -0,0 +1 @@ +loadRelativeToScript("../../tests/non262/shell.js"); diff --git a/js/src/jit-test/lib/orTestHelper.js b/js/src/jit-test/lib/orTestHelper.js new file mode 100644 index 0000000000..bd54d7ac3b --- /dev/null +++ b/js/src/jit-test/lib/orTestHelper.js @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function orTestHelper(a, b, n) +{ + var k = 0; + for (var i = 0; i < n; i++) { + if (a || b) + k += i; + } + return k; +} diff --git a/js/src/jit-test/lib/pretenure.js b/js/src/jit-test/lib/pretenure.js new file mode 100644 index 0000000000..85282680c0 --- /dev/null +++ b/js/src/jit-test/lib/pretenure.js @@ -0,0 +1,80 @@ +// Functions shared by gc/pretenure-*.js tests + +const is64bit = getBuildConfiguration()['pointer-byte-size'] === 8; + +// Count of objects that will exceed the size of the nursery. +const nurseryCount = is64bit ? 25000 : 50000; + +// Count of objects that will exceed the tenured heap collection threshold. +const tenuredCount = is64bit ? 300000 : 600000; + +function setupPretenureTest() { + // The test requires that baseline is enabled and is not bypassed with + // --ion-eager or similar. + let jitOptions = getJitCompilerOptions(); + if (!jitOptions['baseline.enable'] || + jitOptions['ion.warmup.trigger'] <= jitOptions['baseline.warmup.trigger']) { + print("Unsupported JIT options"); + quit(); + } + + // Disable zeal modes that will interfere with this test. + gczeal(0); + + // Restrict nursery size so we can fill it quicker, and ensure it is resized. + gcparam("minNurseryBytes", 1024 * 1024); + gcparam("maxNurseryBytes", 1024 * 1024); + + // Limit allocation threshold so we trigger major GCs sooner. + gcparam("allocationThreshold", 1 /* MB */); + + // Disable incremental GC so there's at most one minor GC per major GC. + gcparam("incrementalGCEnabled", false); + + // Disable balanced heap limits to make the number of GCs predictable. + gcparam("balancedHeapLimitsEnabled", false); + + // Force a nursery collection to apply size parameters. + let o = {}; + + gc(); +} + +function allocateObjects(count, longLived) { + let array = new Array(nurseryCount); + for (let i = 0; i < count; i++) { + let x = {x: i}; + if (longLived) { + array[i % nurseryCount] = x; + } else { + array[0] = x; + } + } + return array; +} + +function allocateArrays(count, longLived) { + let array = new Array(nurseryCount); + for (let i = 0; i < count; i++) { + let x = [i]; + if (longLived) { + array[i % nurseryCount] = x; + } else { + array[0] = x; + } + } + return array; +} + +function gcCounts() { + return { minor: gcparam("minorGCNumber"), + major: gcparam("majorGCNumber") }; +} + +function runTestAndCountCollections(thunk) { + let initialCounts = gcCounts(); + thunk(); + let finalCounts = gcCounts(); + return { minor: finalCounts.minor - initialCounts.minor, + major: finalCounts.major - initialCounts.major }; +} diff --git a/js/src/jit-test/lib/prologue.js b/js/src/jit-test/lib/prologue.js new file mode 100644 index 0000000000..a632a7ba58 --- /dev/null +++ b/js/src/jit-test/lib/prologue.js @@ -0,0 +1,29 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var appendToActual = function(s) { + actual += s + ','; +} + +// Add dummy versions of missing functions and record whether they +// were originally present. +let hasFunction = {}; +for (const name of ["gczeal", + "schedulegc", + "gcslice", + "selectforgc", + "verifyprebarriers", + "verifypostbarriers", + "gcPreserveCode", + "setMarkStackLimit"]) { + const present = name in this; + if (!present) { + this[name] = function() {}; + } + hasFunction[name] = present; +} + +// Set the minimum heap size for parallel marking to zero for testing purposes. +gcparam('parallelMarkingThresholdKB', 0); diff --git a/js/src/jit-test/lib/stepping.js b/js/src/jit-test/lib/stepping.js new file mode 100644 index 0000000000..e9a47cd4f3 --- /dev/null +++ b/js/src/jit-test/lib/stepping.js @@ -0,0 +1,32 @@ +// Test that stepping through a function stops at the expected lines. +// `script` is a string, some JS code that evaluates to a function. +// `expected` is the array of line numbers where stepping is expected to stop +// when we call the function. +function testStepping(script, expected) { + let g = newGlobal({newCompartment: true}); + let f = g.eval(script); + + let log = []; + function maybePause(frame) { + let previousLine = log[log.length - 1]; // note: may be undefined + let line = frame.script.getOffsetLocation(frame.offset).lineNumber; + if (line !== previousLine) + log.push(line); + } + + let dbg = new Debugger(g); + dbg.onEnterFrame = frame => { + // Log this pause (before the first instruction of the function). + maybePause(frame); + + // Log future pauses in the same stack frame. + frame.onStep = function() { maybePause(this); }; + + // Now disable this hook so that we step over function calls, not into them. + dbg.onEnterFrame = undefined; + }; + + f(); + + assertEq(log.join(","), expected.join(",")); +} diff --git a/js/src/jit-test/lib/string.js b/js/src/jit-test/lib/string.js new file mode 100644 index 0000000000..15431ae2c1 --- /dev/null +++ b/js/src/jit-test/lib/string.js @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +if (typeof isHighSurrogate === 'undefined') { + var isHighSurrogate = function isHighSurrogate(s) { + var c = s.charCodeAt(0); + return c >= 0xD800 && c <= 0xDBFF; + } +} + +if (typeof isLowSurrogate === 'undefined') { + var isLowSurrogate = function isLowSurrogate(s) { + var c = s.charCodeAt(0); + return c >= 0xDC00 && c <= 0xDFFF; + } +} + +if (typeof isSurrogatePair === 'undefined') { + var isSurrogatePair = function isSurrogatePair(s) { + return s.length == 2 && isHighSurrogate(s[0]) && isLowSurrogate(s[1]); + } +} diff --git a/js/src/jit-test/lib/syntax.js b/js/src/jit-test/lib/syntax.js new file mode 100644 index 0000000000..5afd1405fb --- /dev/null +++ b/js/src/jit-test/lib/syntax.js @@ -0,0 +1,1284 @@ +function test_syntax(postfixes, check_error, ignore_opts) { + function test_reflect(code, module) { + var options = undefined; + if (module) { + options = {target: "module"}; + } + for (var postfix of postfixes) { + var cur_code = code + postfix; + + var caught = false; + try { + Reflect.parse(cur_code, options); + } catch (e) { + caught = true; + check_error(e, cur_code, "reflect"); + } + assertEq(caught, true); + } + } + + function test_eval(code) { + for (var postfix of postfixes) { + var cur_code = code + postfix; + + var caught = false; + try { + eval(cur_code); + } catch (e) { + caught = true; + check_error(e, cur_code, "eval"); + } + assertEq(caught, true); + } + } + + function test(code, opts={}) { + if (ignore_opts) { + opts = {}; + } + + let no_strict = "no_strict" in opts && opts.no_strict; + let no_fun = "no_fun" in opts && opts.no_fun; + let no_eval = "no_eval" in opts && opts.no_eval; + let module = "module" in opts && opts.module; + + test_reflect(code, module); + if (!no_strict) { + test_reflect("'use strict'; " + code, module); + } + if (!no_fun) { + test_reflect("(function() { " + code, module); + if (!no_strict) { + test_reflect("(function() { 'use strict'; " + code, module); + } + } + + if (!no_eval) { + test_eval(code); + if (!no_strict) { + test_eval("'use strict'; " + code); + } + if (!no_fun) { + test_eval("(function() { " + code); + if (!no_strict) { + test_eval("(function() { 'use strict'; " + code); + } + } + } + } + + function test_fun_arg(arg) { + for (var postfix of postfixes) { + var cur_arg = arg + postfix; + + var caught = false; + try { + new Function(cur_arg, ""); + } catch (e) { + caught = true; + check_error(e, cur_arg, "fun_arg"); + } + assertEq(caught, true); + } + } + + // ==== Statements and declarations ==== + + // ---- Control flow ---- + + // Block + + test("{ "); + test("{ } "); + + test("{ 1 "); + test("{ 1; "); + test("{ 1; } "); + + // break + + test("a: for (;;) { break "); + test("a: for (;;) { break; "); + test("a: for (;;) { break a "); + test("a: for (;;) { break a; "); + + test("a: for (;;) { break\n"); + + // continue + + test("a: for (;;) { continue "); + test("a: for (;;) { continue; "); + test("a: for (;;) { continue a "); + test("a: for (;;) { continue a; "); + + test("a: for (;;) { continue\n"); + + // Empty + + test(""); + test("; "); + + // if...else + + test("if "); + test("if ("); + test("if (x "); + test("if (x) "); + test("if (x) { "); + test("if (x) {} "); + test("if (x) {} else "); + test("if (x) {} else { "); + test("if (x) {} else {} "); + test("if (x) x "); + test("if (x) x; "); + test("if (x) x; else "); + test("if (x) x; else y "); + test("if (x) x; else y; "); + + // switch + + test("switch "); + test("switch ("); + test("switch (x "); + test("switch (x) "); + test("switch (x) { "); + test("switch (x) { case "); + test("switch (x) { case 1 "); + test("switch (x) { case 1: "); + test("switch (x) { case 1: case "); + test("switch (x) { case 1: case 2 "); + test("switch (x) { case 1: case 2: "); + test("switch (x) { case 1: case 2: x "); + test("switch (x) { case 1: case 2: x; "); + test("switch (x) { case 1: case 2: x; break "); + test("switch (x) { case 1: case 2: x; break; "); + test("switch (x) { case 1: case 2: x; break; case "); + test("switch (x) { case 1: case 2: x; break; case 3 "); + test("switch (x) { case 1: case 2: x; break; case 3: y "); + test("switch (x) { case 1: case 2: x; break; case 3: y; "); + test("switch (x) { case 1: case 2: x; break; case 3: y; default "); + test("switch (x) { case 1: case 2: x; break; case 3: y; default: "); + test("switch (x) { case 1: case 2: x; break; case 3: y; default: z "); + test("switch (x) { case 1: case 2: x; break; case 3: y; default: z; "); + test("switch (x) { case 1: case 2: x; break; case 3: y; default: z; } "); + + // throw + + test("throw "); + test("throw x "); + test("throw x; "); + + // try...catch + + test("try "); + test("try { "); + test("try {} "); + test("try {} catch "); + test("try {} catch ( "); + test("try {} catch (e "); + test("try {} catch (e) "); + test("try {} catch (e) { "); + test("try {} catch (e) {} "); + test("try {} catch (e) {} finally "); + test("try {} catch (e) {} finally { "); + test("try {} catch (e) {} finally {} "); + + // ---- Declarations ---- + + // var + + test("var "); + test("var x "); + test("var x = "); + test("var x = 1 "); + test("var x = 1 + "); + test("var x = 1 + 2 "); + test("var x = 1 + 2, "); + test("var x = 1 + 2, y "); + test("var x = 1 + 2, y, "); + test("var x = 1 + 2, y, z "); + test("var x = 1 + 2, y, z; "); + + test("var [ "); + test("var [ x "); + test("var [ x, "); + test("var [ x, ... "); + test("var { "); + test("var { x "); + test("var { x: "); + test("var { x: y "); + test("var { x: y, "); + test("var { x: y } "); + test("var { x: y } = "); + + // let + + test("let "); + test("let x "); + test("let x = "); + test("let x = 1 "); + test("let x = 1 + "); + test("let x = 1 + 2 "); + test("let x = 1 + 2, "); + test("let x = 1 + 2, y "); + test("let x = 1 + 2, y, "); + test("let x = 1 + 2, y, z "); + test("let x = 1 + 2, y, z; "); + + test("let [ "); + test("let [ x "); + test("let [ x, "); + test("let [ x, ... "); + test("let { "); + test("let { x "); + test("let { x: "); + test("let { x: y "); + test("let { x: y, "); + test("let { x: y } "); + test("let { x: y } = "); + + // const + + test("const "); + test("const x "); + test("const x = "); + test("const x = 1 "); + test("const x = 1 + "); + test("const x = 1 + 2 "); + test("const x = 1 + 2, "); + test("const x = 1 + 2, y = 0"); + test("const x = 1 + 2, y = 0, "); + test("const x = 1 + 2, y = 0, z = 0 "); + test("const x = 1 + 2, y = 0, z = 0; "); + + test("const [ "); + test("const [ x "); + test("const [ x, "); + test("const [ x, ... "); + test("const { "); + test("const { x "); + test("const { x: "); + test("const { x: y "); + test("const { x: y, "); + test("const { x: y } "); + test("const { x: y } = "); + + // ---- Functions ---- + + // function + + test("function "); + test("function f "); + test("function f( "); + test("function f(x "); + test("function f(x, "); + test("function f(x, [ "); + test("function f(x, [y "); + test("function f(x, [y, "); + test("function f(x, [y, { "); + test("function f(x, [y, {z "); + test("function f(x, [y, {z: "); + test("function f(x, [y, {z: zz "); + test("function f(x, [y, {z: zz, "); + test("function f(x, [y, {z: zz, w "); + test("function f(x, [y, {z: zz, w} "); + test("function f(x, [y, {z: zz, w}] "); + test("function f(x, [y, {z: zz, w}], "); + test("function f(x, [y, {z: zz, w}], v "); + test("function f(x, [y, {z: zz, w}], v= "); + test("function f(x, [y, {z: zz, w}], v=1 "); + test("function f(x, [y, {z: zz, w}], v=1, "); + test("function f(x, [y, {z: zz, w}], v=1, ... "); + test("function f(x, [y, {z: zz, w}], v=1, ...t "); + test("function f(x, [y, {z: zz, w}], v=1, ...t) "); + test("function f(x, [y, {z: zz, w}], v=1, ...t) {"); + test("function f(x, [y, {z: zz, w}], v=1, ...t) { x "); + test("function f(x, [y, {z: zz, w}], v=1, ...t) { x; "); + test("function f(x, [y, {z: zz, w}], v=1, ...t) { x; } "); + + // star function + + test("function* "); + test("function* f "); + test("function* f( "); + test("function* f(x "); + test("function* f(x, "); + test("function* f(x, ... "); + test("function* f(x, ...t "); + test("function* f(x, ...t) "); + test("function* f(x, ...t) {"); + test("function* f(x, ...t) { x "); + test("function* f(x, ...t) { x; "); + test("function* f(x, ...t) { x; } "); + + // return + + test("function f() { return "); + test("function f() { return 1 "); + test("function f() { return 1; "); + test("function f() { return 1; } "); + test("function f() { return; "); + test("function f() { return\n"); + + // yield + + test("function* f() { yield "); + test("function* f() { yield 1 "); + test("function* f() { yield* "); + test("function* f() { yield* 1 "); + + test("function* f() { yield\n"); + test("function* f() { yield*\n"); + + // ---- Iterations ---- + + // do...while + + test("do "); + test("do {"); + test("do {} "); + test("do {} while "); + test("do {} while ( "); + test("do {} while (x "); + test("do {} while (x) "); + test("do {} while (x); "); + + test("do x "); + test("do x; "); + test("do x; while "); + + // for + + test("for "); + test("for ("); + test("for (x "); + test("for (x; "); + test("for (x; y "); + test("for (x; y; "); + test("for (x; y; z "); + test("for (x; y; z) "); + test("for (x; y; z) { "); + test("for (x; y; z) {} "); + + test("for (x; y; z) x "); + test("for (x; y; z) x; "); + + test("for (var "); + test("for (var x "); + test("for (var x = "); + test("for (var x = y "); + test("for (var x = y; "); + + test("for (let "); + test("for (let x "); + test("for (let x = "); + test("for (let x = y "); + test("for (let x = y; "); + + // for...in + + test("for (x in "); + test("for (x in y "); + test("for (x in y) "); + + test("for (var x in "); + test("for (var x in y "); + test("for (var x in y) "); + + test("for (let x in "); + test("for (let x in y "); + test("for (let x in y) "); + + // for...of + + test("for (x of "); + test("for (x of y "); + test("for (x of y) "); + + test("for (var x of "); + test("for (var x of y "); + test("for (var x of y) "); + + test("for (let x of "); + test("for (let x of y "); + test("for (let x of y) "); + + // while + + test("while "); + test("while ("); + test("while (x "); + test("while (x) "); + test("while (x) { "); + test("while (x) {} "); + + test("while (x) x "); + test("while (x) x; "); + + // ---- Others ---- + + // debugger + + test("debugger "); + test("debugger; "); + + // export + + var opts = { no_fun: true, no_eval: true, module: true }; + test("export ", opts); + test("export { ", opts); + test("export { x ", opts); + test("export { x, ", opts); + test("export { x, y ", opts); + test("export { x, y as ", opts); + test("export { x, y as z ", opts); + test("export { x, y as z } ", opts); + test("export { x, y as z } from ", opts); + test("export { x, y as z } from 'a' ", opts); + test("export { x, y as z } from 'a'; ", opts); + + test("export * ", opts); + test("export * from ", opts); + test("export * from 'a' ", opts); + test("export * from 'a'; ", opts); + + test("export * ", opts); + test("export * as ", opts); + test("export * as ns ", opts); + test("export * as ns from ", opts); + test("export * as ns from 'a' ", opts); + test("export * as ns from 'a'; ", opts); + + test("export function ", opts); + test("export function f ", opts); + test("export function f( ", opts); + test("export function f() ", opts); + test("export function f() { ", opts); + test("export function f() {} ", opts); + test("export function f() {}; ", opts); + + test("export var ", opts); + test("export var a ", opts); + test("export var a = ", opts); + test("export var a = 1 ", opts); + test("export var a = 1, ", opts); + test("export var a = 1, b ", opts); + test("export var a = 1, b = ", opts); + test("export var a = 1, b = 2 ", opts); + test("export var a = 1, b = 2; ", opts); + + test("export let ", opts); + test("export let a ", opts); + test("export let a = ", opts); + test("export let a = 1 ", opts); + test("export let a = 1, ", opts); + test("export let a = 1, b ", opts); + test("export let a = 1, b = ", opts); + test("export let a = 1, b = 2 ", opts); + test("export let a = 1, b = 2; ", opts); + + test("export const ", opts); + test("export const a ", opts); + test("export const a = ", opts); + test("export const a = 1 ", opts); + test("export const a = 1, ", opts); + test("export const a = 1, b ", opts); + test("export const a = 1, b = ", opts); + test("export const a = 1, b = 2 ", opts); + test("export const a = 1, b = 2; ", opts); + + test("export class ", opts); + test("export class Foo ", opts); + test("export class Foo { ", opts); + test("export class Foo { constructor ", opts); + test("export class Foo { constructor( ", opts); + test("export class Foo { constructor() ", opts); + test("export class Foo { constructor() { ", opts); + test("export class Foo { constructor() {} ", opts); + test("export class Foo { constructor() {} } ", opts); + test("export class Foo { constructor() {} }; ", opts); + + test("export default ", opts); + test("export default 1 ", opts); + test("export default 1; ", opts); + + test("export default function ", opts); + test("export default function() ", opts); + test("export default function() { ", opts); + test("export default function() {} ", opts); + test("export default function() {}; ", opts); + + test("export default function foo ", opts); + test("export default function foo( ", opts); + test("export default function foo() ", opts); + test("export default function foo() { ", opts); + test("export default function foo() {} ", opts); + test("export default function foo() {}; ", opts); + + test("export default class ", opts); + test("export default class { ", opts); + test("export default class { constructor ", opts); + test("export default class { constructor( ", opts); + test("export default class { constructor() ", opts); + test("export default class { constructor() { ", opts); + test("export default class { constructor() {} ", opts); + test("export default class { constructor() {} } ", opts); + test("export default class { constructor() {} }; ", opts); + + test("export default class Foo ", opts); + test("export default class Foo { ", opts); + test("export default class Foo { constructor ", opts); + test("export default class Foo { constructor( ", opts); + test("export default class Foo { constructor() ", opts); + test("export default class Foo { constructor() { ", opts); + test("export default class Foo { constructor() {} ", opts); + test("export default class Foo { constructor() {} } ", opts); + test("export default class Foo { constructor() {} }; ", opts); + + // import + + test("import ", opts); + test("import x ", opts); + test("import x from ", opts); + test("import x from 'a' ", opts); + test("import x from 'a'; ", opts); + + test("import { ", opts); + test("import { x ", opts); + test("import { x, ", opts); + test("import { x, y ", opts); + test("import { x, y } ", opts); + test("import { x, y } from ", opts); + test("import { x, y } from 'a' ", opts); + test("import { x, y } from 'a'; ", opts); + + test("import { x as ", opts); + test("import { x as y ", opts); + test("import { x as y } ", opts); + test("import { x as y } from ", opts); + test("import { x as y } from 'a' ", opts); + test("import { x as y } from 'a'; ", opts); + + test("import 'a' ", opts); + test("import 'a'; ", opts); + + test("import * ", opts); + test("import * as ", opts); + test("import * as a ", opts); + test("import * as a from ", opts); + test("import * as a from 'a' ", opts); + test("import * as a from 'a'; ", opts); + + test("import a ", opts); + test("import a, ", opts); + test("import a, * ", opts); + test("import a, * as ", opts); + test("import a, * as b ", opts); + test("import a, * as b from ", opts); + test("import a, * as b from 'c' ", opts); + test("import a, * as b from 'c'; ", opts); + + test("import a, { ", opts); + test("import a, { b ", opts); + test("import a, { b } ", opts); + test("import a, { b } from ", opts); + test("import a, { b } from 'c' ", opts); + test("import a, { b } from 'c'; ", opts); + + // label + + test("a "); + test("a: "); + + // with + + opts = { no_strict: true }; + test("with ", opts); + test("with (", opts); + test("with (x ", opts); + test("with (x) ", opts); + test("with (x) { ", opts); + test("with (x) {} ", opts); + + test("with (x) x ", opts); + test("with (x) x; ", opts); + + // ==== Expressions and operators ==== + + // ---- Primary expressions ---- + + // this + + test("this "); + + // function + + test("(function "); + test("(function ( "); + test("(function (x "); + test("(function (x, "); + test("(function (x, ... "); + test("(function (x, ...t "); + test("(function (x, ...t) "); + test("(function (x, ...t) {"); + test("(function (x, ...t) { x "); + test("(function (x, ...t) { x; "); + test("(function (x, ...t) { x; } "); + test("(function (x, ...t) { x; }) "); + + // star function + + test("(function* "); + test("(function* ( "); + test("(function* (x "); + test("(function* (x, "); + test("(function* (x, ... "); + test("(function* (x, ...t "); + test("(function* (x, ...t) "); + test("(function* (x, ...t) {"); + test("(function* (x, ...t) { x "); + test("(function* (x, ...t) { x; "); + test("(function* (x, ...t) { x; } "); + test("(function* (x, ...t) { x; }) "); + + // Array literal + + test("[ "); + test("[] "); + test("[1 "); + test("[1, "); + test("[1, ... "); + test("[1, ...x "); + test("[1, ...x] "); + + // object + + test("({ "); + test("({ x "); + test("({ x: "); + test("({ x: 1 "); + test("({ x: 1, "); + test("({ x: 1, y "); + test("({ x: 1, y: "); + test("({ x: 1, y: 2 "); + test("({ x: 1, y: 2, "); + test("({ x: 1, y: 2, z "); + test("({ x: 1, y: 2, z, "); + test("({ x: 1, y: 2, z, w "); + test("({ x: 1, y: 2, z, w } "); + test("({ x: 1, y: 2, z, w }) "); + + // object: computed property + + test("({ ["); + test("({ [k "); + test("({ [k] "); + test("({ [k]: "); + test("({ [k]: 1 "); + test("({ [k]: 1, "); + + // object: getter + + test("({ get "); + test("({ get p "); + test("({ get p( "); + test("({ get p() "); + test("({ get p() { "); + test("({ get p() {} "); + test("({ get p() {}, "); + test("({ get p() {}, } "); + + test("({ get [ "); + test("({ get [p "); + test("({ get [p] "); + test("({ get [p]( "); + test("({ get [p]() "); + + // object: setter + + test("({ set "); + test("({ set p "); + test("({ set p( "); + test("({ set p(v "); + test("({ set p(v) "); + test("({ set p(v) { "); + test("({ set p(v) {} "); + + test("({ set [ "); + test("({ set [p "); + test("({ set [p] "); + test("({ set [p]( "); + test("({ set [p](v "); + test("({ set [p](v) "); + + // object: method + + test("({ m "); + test("({ m( "); + test("({ m() "); + test("({ m() { "); + test("({ m() {} "); + test("({ m() {}, "); + + test("({ [ "); + test("({ [m "); + test("({ [m] "); + test("({ [m]( "); + test("({ [m]() "); + test("({ [m]() { "); + test("({ [m]() {} "); + test("({ [m]() {}, "); + + test("({ * "); + test("({ *m "); + test("({ *m( "); + test("({ *m() "); + test("({ *m() { "); + test("({ *m() {} "); + test("({ *m() {}, "); + + test("({ *[ "); + test("({ *[m "); + test("({ *[m] "); + test("({ *[m]( "); + test("({ *[m]() "); + test("({ *[m]() { "); + test("({ *[m]() {} "); + test("({ *[m]() {}, "); + + test("({ * get "); + test("({ * get ( "); + test("({ * get () "); + test("({ * get () { "); + test("({ * get () {} "); + test("({ * get () {}, "); + + test("({ * set "); + test("({ * set ( "); + test("({ * set () "); + test("({ * set () { "); + test("({ * set () {} "); + test("({ * set () {}, "); + + // Regular expression literal + + test("/a/ "); + test("/a/g "); + + // ---- Left-hand-side expressions ---- + + // property access + + test("a[ "); + test("a[1 "); + test("a[1] "); + + test("a. "); + test("a.b "); + test("a.b; "); + + // new + + test("new "); + test("new f "); + test("new f( "); + test("new f() "); + test("new f(); "); + + // ---- Increment and decrement ---- + + test("a ++ "); + test("a ++; "); + + test("-- "); + test("-- a "); + test("-- a; "); + + // ---- Unary operators ---- + + // delete + + test("delete "); + test("delete a "); + test("delete a[ "); + test("delete a[b "); + test("delete a[b] "); + test("delete a[b]; "); + + test("delete ( "); + test("delete (a "); + test("delete (a[ "); + test("delete (a[b "); + test("delete (a[b] "); + test("delete (a[b]) "); + test("delete (a[b]); "); + + // void + + test("void "); + test("void a "); + test("void a; "); + + test("void ("); + test("void (a "); + test("void (a) "); + test("void (a); "); + + // typeof + + test("typeof "); + test("typeof a "); + test("typeof a; "); + + test("typeof ("); + test("typeof (a "); + test("typeof (a) "); + test("typeof (a); "); + + // - + + test("- "); + test("- 1 "); + test("- 1; "); + + // + + + test("+ "); + test("+ 1 "); + test("+ 1; "); + + // ---- Arithmetic operators ---- + + // + + + test("1 + "); + test("1 + 1 "); + test("1 + 1; "); + + // ---- Relational operators ---- + + // in + + test("a in "); + test("a in b "); + test("a in b; "); + + // instanceof + + test("a instanceof "); + test("a instanceof b "); + test("a instanceof b; "); + + // ---- Equality operators ---- + + // == + + test("1 == "); + test("1 == 1 "); + test("1 == 1; "); + + // ---- Bitwise shift operators ---- + + // << + + test("1 << "); + test("1 << 1 "); + test("1 << 1; "); + + // ---- Binary bitwise operators ---- + + // & + + test("1 & "); + test("1 & 1 "); + test("1 & 1; "); + + // ---- Binary logical operators ---- + + // || + + test("1 || "); + test("1 || 1 "); + test("1 || 1; "); + + // ---- Conditional (ternary) operator ---- + + test("1 ? "); + test("1 ? 2 "); + test("1 ? 2 : "); + test("1 ? 2 : 3 "); + test("1 ? 2 : 3; "); + + // ---- Assignment operators ---- + + test("x = "); + test("x = 1 "); + test("x = 1 + "); + test("x = 1 + 2 "); + test("x = 1 + 2; "); + + // ---- Comma operator ---- + + test("1, "); + test("1, 2 "); + test("1, 2; "); + + // ---- Functions ---- + + // Arrow functions + + test("a => "); + test("a => 1 "); + test("a => 1; "); + test("a => { "); + test("a => {} "); + test("a => {}; "); + + test("( "); + test("() "); + test("() => "); + + test("(..."); + test("(...a "); + test("(...a) "); + test("(...a) => "); + + test("([ "); + test("([a "); + test("([a] "); + test("([a]) "); + test("([a]) => "); + + test("({ "); + test("({a "); + test("({a} "); + test("({a}) "); + test("({a}) => "); + test("({a: "); + test("({a: b "); + test("({a: b, "); + test("({a: b} "); + test("({a: b}) "); + test("({a: b}) => "); + + // ---- Class declaration ---- + + test("class "); + test("class a "); + test("class a { "); + test("class a { constructor "); + test("class a { constructor( "); + test("class a { constructor() "); + test("class a { constructor() { "); + test("class a { constructor() { } "); + test("class a { constructor() { } } "); + + test("class a { constructor() { } static "); + test("class a { constructor() { } static m "); + test("class a { constructor() { } static m( "); + test("class a { constructor() { } static m() "); + test("class a { constructor() { } static m() { "); + test("class a { constructor() { } static m() {} "); + test("class a { constructor() { } static m() {} } "); + + test("class a { constructor() { } static ( "); + test("class a { constructor() { } static () "); + test("class a { constructor() { } static () { "); + test("class a { constructor() { } static () {} "); + test("class a { constructor() { } static () {} } "); + + test("class a { constructor() { } static get "); + test("class a { constructor() { } static get p "); + test("class a { constructor() { } static get p( "); + test("class a { constructor() { } static get p() "); + test("class a { constructor() { } static get p() { "); + test("class a { constructor() { } static get p() {} "); + test("class a { constructor() { } static get p() {} } "); + + test("class a { constructor() { } static set "); + test("class a { constructor() { } static set p "); + test("class a { constructor() { } static set p( "); + test("class a { constructor() { } static set p(v "); + test("class a { constructor() { } static set p(v) "); + test("class a { constructor() { } static set p(v) { "); + test("class a { constructor() { } static set p(v) {} "); + test("class a { constructor() { } static set p(v) {} } "); + + test("class a { constructor() { } * "); + test("class a { constructor() { } *m "); + test("class a { constructor() { } *m( "); + test("class a { constructor() { } *m() "); + test("class a { constructor() { } *m() { "); + test("class a { constructor() { } *m() {} "); + test("class a { constructor() { } *m() {} } "); + + test("class a { constructor() { } static * "); + test("class a { constructor() { } static *m "); + test("class a { constructor() { } static *m( "); + test("class a { constructor() { } static *m() "); + test("class a { constructor() { } static *m() { "); + test("class a { constructor() { } static *m() {} "); + test("class a { constructor() { } static *m() {} } "); + + test("class a extends "); + test("class a extends b "); + test("class a extends b { "); + + test("class a extends ( "); + test("class a extends ( b "); + test("class a extends ( b ) "); + test("class a extends ( b ) { "); + + // ---- Class expression ---- + + test("( class "); + test("( class a "); + test("( class a { "); + test("( class a { constructor "); + test("( class a { constructor( "); + test("( class a { constructor() "); + test("( class a { constructor() { "); + test("( class a { constructor() { } "); + test("( class a { constructor() { } } "); + test("( class a { constructor() { } } ) "); + + test("(class a extends "); + test("(class a extends b "); + test("(class a extends b { "); + + test("(class a extends ( "); + test("(class a extends ( b "); + test("(class a extends ( b ) "); + test("(class a extends ( b ) { "); + + test("( class { "); + test("( class { constructor "); + test("( class { constructor( "); + test("( class { constructor() "); + test("( class { constructor() { "); + test("( class { constructor() { } "); + test("( class { constructor() { } } "); + test("( class { constructor() { } } ) "); + + test("(class extends "); + test("(class extends b "); + test("(class extends b { "); + + test("(class extends ( "); + test("(class extends ( b "); + test("(class extends ( b ) "); + test("(class extends ( b ) { "); + + // ---- Other ---- + + // Literals + + test("a "); + test("1 "); + test("1. "); + test("1.2 "); + test("true "); + test("false "); + test("\"a\" "); + test("'a' "); + test("null "); + + // Template strings + + test("`${ "); + test("`${a "); + test("`${a}` "); + + // Function calls + + test("f( "); + test("f() "); + test("f(); "); + + test("f(... "); + test("f(...x "); + test("f(...x) "); + + // Function constructors + + test_fun_arg(""); + test_fun_arg("a "); + test_fun_arg("... "); + test_fun_arg("...a "); + + // ==== Legacy ==== + + // ==== asm.js ==== + + test("(function() { 'use asm'; "); + test("(function() { 'use asm'; var "); + test("(function() { 'use asm'; var a "); + test("(function() { 'use asm'; var a = "); + test("(function() { 'use asm'; var a = 1 "); + test("(function() { 'use asm'; var a = 1; "); + test("(function() { 'use asm'; var a = 1; function "); + test("(function() { 'use asm'; var a = 1; function f "); + test("(function() { 'use asm'; var a = 1; function f( "); + test("(function() { 'use asm'; var a = 1; function f() "); + test("(function() { 'use asm'; var a = 1; function f() { "); + test("(function() { 'use asm'; var a = 1; function f() { } "); + test("(function() { 'use asm'; var a = 1; function f() { } var "); + test("(function() { 'use asm'; var a = 1; function f() { } var tbl "); + test("(function() { 'use asm'; var a = 1; function f() { } var tbl = "); + test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [ "); + test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f "); + test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f] "); + test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; "); + test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; return "); + test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; return f "); + test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; return f; "); + test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; return f; } "); + test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; return f; }) "); + test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; return f; }); "); + + // ==== async/await ==== + + // async/await function decralation + + test("async "); + test("async function "); + test("async function A "); + test("async function A( "); + test("async function A() "); + test("async function A(a "); + test("async function A(a) "); + test("async function A(a) { "); + test("async function A(a) {} "); + test("async function A(a) { await "); + test("async function A(a) { await X "); + test("async function A(a) { await X; "); + test("async function A(a) { await X; } "); + test("async function A(a) { await await "); + test("async function A(a) { await await await "); + test("async function A(a) { await await await X "); + test("async function A(a) { await await await X; "); + test("async function A(a) { await await await X; } "); + + opts = { no_fun: true, no_eval: true, module: true }; + test("export default async ", opts); + test("export default async function ", opts); + test("export default async function ( ", opts); + test("export default async function () ", opts); + test("export default async function (a ", opts); + test("export default async function (a) ", opts); + test("export default async function (a) { ", opts); + test("export default async function (a) {} ", opts); + test("export default async function (a) { await ", opts); + test("export default async function (a) { await X ", opts); + test("export default async function (a) { await X; ", opts); + test("export default async function (a) { await X; } ", opts); + + // async/await function expression + + test("(async "); + test("(async function "); + test("(async function A "); + test("(async function A( "); + test("(async function A() "); + test("(async function A(a "); + test("(async function A(a) "); + test("(async function A(a) { "); + test("(async function A(a) {} "); + test("(async function A(a) { await "); + test("(async function A(a) { await X "); + test("(async function A(a) { await X; "); + test("(async function A(a) { await X; } "); + test("(async function A(a) { await X; }) "); + + test("(async function ( "); + test("(async function () "); + test("(async function (a "); + test("(async function (a) "); + test("(async function (a) { "); + test("(async function (a) {} "); + test("(async function (a) { await "); + test("(async function (a) { await X "); + test("(async function (a) { await X; "); + test("(async function (a) { await X; } "); + test("(async function (a) { await X; }) "); + + // async/await method + + test("({ async "); + test("({ async m "); + test("({ async m( "); + test("({ async m() "); + test("({ async m() { "); + test("({ async m() {} "); + test("({ async m() {}, "); + + test("class X { async "); + test("class X { async m "); + test("class X { async m( "); + test("class X { async m() "); + test("class X { async m() { "); + test("class X { async m() {} "); + + test("class X { static async "); + test("class X { static async m "); + test("class X { static async m( "); + test("class X { static async m() "); + test("class X { static async m() { "); + test("class X { static async m() {} "); + + // async/await arrow + + test("(async a "); + test("(async a => "); + test("(async a => b "); + test("(async a => b) "); + + test("(async a => { "); + test("(async a => { b "); + test("(async a => { b } "); + test("(async a => { b }) "); + + test("(async ( "); + test("(async (a "); + test("(async (a) "); + test("(async (a) => "); + test("(async (a) => b "); + test("(async (a) => b) "); + test("(async (a, "); + test("(async (a, b "); + test("(async (a, b) "); + test("(async (a, b) => "); + test("(async (a, b) => b "); + test("(async (a, b) => b) "); + + test("(async ([ "); + test("(async ([a "); + test("(async ([a] "); + test("(async ([a]) "); + test("(async ([a]) => "); + test("(async ([a]) => b "); + test("(async ([a]) => b) "); + test("(async ([a, "); + test("(async ([a, b "); + test("(async ([a, b] "); + test("(async ([a, b]) "); + test("(async ([a, b]) => "); + test("(async ([a, b]) => b "); + test("(async ([a, b]) => b) "); + + test("(async ({ "); + test("(async ({a "); + test("(async ({a} "); + test("(async ({a}) "); + test("(async ({a}) => "); + test("(async ({a}) => b "); + test("(async ({a}) => b) "); + test("(async ({a, "); + test("(async ({a, b "); + test("(async ({a, b} "); + test("(async ({a, b}) "); + test("(async ({a, b}) => "); + test("(async ({a, b}) => b "); + test("(async ({a, b}) => b) "); +} diff --git a/js/src/jit-test/lib/wasm-binary.js b/js/src/jit-test/lib/wasm-binary.js new file mode 100644 index 0000000000..93ddc0571d --- /dev/null +++ b/js/src/jit-test/lib/wasm-binary.js @@ -0,0 +1,555 @@ +// MagicNumber = 0x6d736100; +const magic0 = 0x00; // '\0' +const magic1 = 0x61; // 'a' +const magic2 = 0x73; // 's' +const magic3 = 0x6d; // 'm' + +// EncodingVersion +const encodingVersion = 0x1; +const ver0 = (encodingVersion >>> 0) & 0xff; +const ver1 = (encodingVersion >>> 8) & 0xff; +const ver2 = (encodingVersion >>> 16) & 0xff; +const ver3 = (encodingVersion >>> 24) & 0xff; + +// Section opcodes +const userDefinedId = 0; +const typeId = 1; +const importId = 2; +const functionId = 3; +const tableId = 4; +const memoryId = 5; +const globalId = 6; +const exportId = 7; +const startId = 8; +const elemId = 9; +const codeId = 10; +const dataId = 11; +const dataCountId = 12; +const tagId = 13; + +// User-defined section names +const nameName = "name"; + +// Name section name types +const nameTypeModule = 0; +const nameTypeFunction = 1; +const nameTypeLocal = 2; +const nameTypeTag = 3; + +// Type codes +const I32Code = 0x7f; +const I64Code = 0x7e; +const F32Code = 0x7d; +const F64Code = 0x7c; +const V128Code = 0x7b; +const AnyFuncCode = 0x70; +const ExternRefCode = 0x6f; +const EqRefCode = 0x6d; +const OptRefCode = 0x6c; +const FuncCode = 0x60; +const VoidCode = 0x40; + +// Opcodes +const UnreachableCode = 0x00 +const BlockCode = 0x02; +const TryCode = 0x06; +const CatchCode = 0x07; +const ThrowCode = 0x08; +const RethrowCode = 0x09; +const EndCode = 0x0b; +const ReturnCode = 0x0f; +const CallCode = 0x10; +const CallIndirectCode = 0x11; +const DelegateCode = 0x18; +const DropCode = 0x1a; +const SelectCode = 0x1b; +const LocalGetCode = 0x20; +const I32Load = 0x28; +const I64Load = 0x29; +const F32Load = 0x2a; +const F64Load = 0x2b; +const I32Load8S = 0x2c; +const I32Load8U = 0x2d; +const I32Load16S = 0x2e; +const I32Load16U = 0x2f; +const I64Load8S = 0x30; +const I64Load8U = 0x31; +const I64Load16S = 0x32; +const I64Load16U = 0x33; +const I64Load32S = 0x34; +const I64Load32U = 0x35; +const I32Store = 0x36; +const I64Store = 0x37; +const F32Store = 0x38; +const F64Store = 0x39; +const I32Store8 = 0x3a; +const I32Store16 = 0x3b; +const I64Store8 = 0x3c; +const I64Store16 = 0x3d; +const I64Store32 = 0x3e; +const GrowMemoryCode = 0x40; +const I32ConstCode = 0x41; +const I64ConstCode = 0x42; +const F32ConstCode = 0x43; +const F64ConstCode = 0x44; +const I32AddCode = 0x6a; +const I32DivSCode = 0x6d; +const I32DivUCode = 0x6e; +const I32RemSCode = 0x6f; +const I32RemUCode = 0x70; +const I32TruncSF32Code = 0xa8; +const I32TruncUF32Code = 0xa9; +const I32TruncSF64Code = 0xaa; +const I32TruncUF64Code = 0xab; +const I64TruncSF32Code = 0xae; +const I64TruncUF32Code = 0xaf; +const I64TruncSF64Code = 0xb0; +const I64TruncUF64Code = 0xb1; +const I64DivSCode = 0x7f; +const I64DivUCode = 0x80; +const I64RemSCode = 0x81; +const I64RemUCode = 0x82; +const RefNullCode = 0xd0; +const RefIsNullCode = 0xd1; +const RefFuncCode = 0xd2; + +// SIMD opcodes +const V128LoadCode = 0x00; +const V128StoreCode = 0x0b; +const I32x4DotSI16x8Code = 0xba; +const F32x4CeilCode = 0xd8; +const F32x4FloorCode = 0xd9; +const F32x4TruncCode = 0xda; +const F32x4NearestCode = 0xdb; +const F64x2CeilCode = 0xdc; +const F64x2FloorCode = 0xdd; +const F64x2TruncCode = 0xde; +const F64x2NearestCode = 0xdf; +const F32x4PMinCode = 0xea; +const F32x4PMaxCode = 0xeb; +const F64x2PMinCode = 0xf6; +const F64x2PMaxCode = 0xf7; +const V128Load32ZeroCode = 0xfc; +const V128Load64ZeroCode = 0xfd; + +// Relaxed SIMD opcodes. +const I8x16RelaxedSwizzleCode = 0x100; +const I32x4RelaxedTruncSSatF32x4Code = 0x101; +const I32x4RelaxedTruncUSatF32x4Code = 0x102; +const I32x4RelaxedTruncSatF64x2SZeroCode = 0x103; +const I32x4RelaxedTruncSatF64x2UZeroCode = 0x104; +const F32x4RelaxedFmaCode = 0x105; +const F32x4RelaxedFnmaCode = 0x106; +const F64x2RelaxedFmaCode = 0x107; +const F64x2RelaxedFnmaCode = 0x108; +const I8x16RelaxedLaneSelectCode = 0x109; +const I16x8RelaxedLaneSelectCode = 0x10a; +const I32x4RelaxedLaneSelectCode = 0x10b; +const I64x2RelaxedLaneSelectCode = 0x10c; +const F32x4RelaxedMinCode = 0x10d; +const F32x4RelaxedMaxCode = 0x10e; +const F64x2RelaxedMinCode = 0x10f; +const F64x2RelaxedMaxCode = 0x110; +const I16x8RelaxedQ15MulrS = 0x111; +const I16x8DotI8x16I7x16S = 0x112; +const I32x4DotI8x16I7x16AddS = 0x113; + +const FirstInvalidOpcode = 0xc5; +const LastInvalidOpcode = 0xfa; +const GcPrefix = 0xfb; +const MiscPrefix = 0xfc; +const SimdPrefix = 0xfd; +const ThreadPrefix = 0xfe; +const MozPrefix = 0xff; + +// See WasmConstants.h for documentation. +// Limit this to a group of 8 per line. + +const definedOpcodes = + [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + ...(wasmExceptionsEnabled() ? [0x06, 0x07, 0x08, 0x09] : []), + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, + ...(wasmFunctionReferencesEnabled() ? [0x14] : []), + ...(wasmExceptionsEnabled() ? [0x18, 0x19] : []), + 0x1a, 0x1b, 0x1c, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, + 0xf0, + 0xfb, 0xfc, 0xfd, 0xfe, 0xff ]; + +const undefinedOpcodes = (function () { + let a = []; + let j = 0; + let i = 0; + while (i < 256) { + while (definedOpcodes[j] > i) + a.push(i++); + assertEq(definedOpcodes[j], i); + i++; + j++; + } + assertEq(definedOpcodes.length + a.length, 256); + return a; +})(); + +// Secondary opcode bytes for misc prefix +const MemoryInitCode = 0x08; // Pending +const DataDropCode = 0x09; // Pending +const MemoryCopyCode = 0x0a; // Pending +const MemoryFillCode = 0x0b; // Pending +const TableInitCode = 0x0c; // Pending +const ElemDropCode = 0x0d; // Pending +const TableCopyCode = 0x0e; // Pending + +const StructNew = 0x00; // UNOFFICIAL +const StructGet = 0x03; // UNOFFICIAL +const StructSet = 0x06; // UNOFFICIAL + +// DefinitionKind +const FunctionCode = 0x00; +const TableCode = 0x01; +const MemoryCode = 0x02; +const GlobalCode = 0x03; +const TagCode = 0x04; + +// ResizableFlags +const HasMaximumFlag = 0x1; + +function toU8(array) { + for (let b of array) + assertEq(b < 256, true); + return Uint8Array.from(array); +} + +function varU32(u32) { + assertEq(u32 >= 0, true); + assertEq(u32 < Math.pow(2,32), true); + var bytes = []; + do { + var byte = u32 & 0x7f; + u32 >>>= 7; + if (u32 != 0) + byte |= 0x80; + bytes.push(byte); + } while (u32 != 0); + return bytes; +} + +function varS32(s32) { + assertEq(s32 >= -Math.pow(2,31), true); + assertEq(s32 < Math.pow(2,31), true); + var bytes = []; + do { + var byte = s32 & 0x7f; + s32 >>= 7; + if (s32 != 0 && s32 != -1) + byte |= 0x80; + bytes.push(byte); + } while (s32 != 0 && s32 != -1); + return bytes; +} + +function moduleHeaderThen(...rest) { + return [magic0, magic1, magic2, magic3, ver0, ver1, ver2, ver3, ...rest]; +} + +function string(name) { + var nameBytes = name.split('').map(c => { + var code = c.charCodeAt(0); + assertEq(code < 128, true); // TODO + return code + }); + return varU32(nameBytes.length).concat(nameBytes); +} + +function encodedString(name, len) { + var name = unescape(encodeURIComponent(name)); // break into string of utf8 code points + var nameBytes = name.split('').map(c => c.charCodeAt(0)); // map to array of numbers + return varU32(len === undefined ? nameBytes.length : len).concat(nameBytes); +} + +function moduleWithSections(sectionArray) { + var bytes = moduleHeaderThen(); + for (let section of sectionArray) { + bytes.push(section.name); + bytes.push(...varU32(section.body.length)); + bytes.push(...section.body); + } + return toU8(bytes); +} + +function sigSection(sigs) { + var body = []; + body.push(...varU32(sigs.length)); + for (let sig of sigs) { + body.push(...varU32(FuncCode)); + body.push(...varU32(sig.args.length)); + for (let arg of sig.args) + body.push(...varU32(arg)); + if (sig.ret == VoidCode) { + body.push(...varU32(0)); + } else if (typeof sig.ret == "number") { + body.push(...varU32(1)); + body.push(...varU32(sig.ret)); + } else { + body.push(...varU32(sig.ret.length)); + for (let r of sig.ret) + body.push(...varU32(r)); + } + } + return { name: typeId, body }; +} + +function declSection(decls) { + var body = []; + body.push(...varU32(decls.length)); + for (let decl of decls) + body.push(...varU32(decl)); + return { name: functionId, body }; +} + +function funcBody(func, withEndCode=true) { + var body = varU32(func.locals.length); + for (let local of func.locals) + body.push(...varU32(local)); + body = body.concat(...func.body); + if (withEndCode) + body.push(EndCode); + body.splice(0, 0, ...varU32(body.length)); + return body; +} + +function bodySection(bodies) { + var body = varU32(bodies.length).concat(...bodies); + return { name: codeId, body }; +} + +function importSection(imports) { + var body = []; + body.push(...varU32(imports.length)); + for (let imp of imports) { + body.push(...string(imp.module)); + body.push(...string(imp.func)); + body.push(...varU32(FunctionCode)); + body.push(...varU32(imp.sigIndex)); + } + return { name: importId, body }; +} + +function exportSection(exports) { + var body = []; + body.push(...varU32(exports.length)); + for (let exp of exports) { + body.push(...string(exp.name)); + if (exp.hasOwnProperty("funcIndex")) { + body.push(...varU32(FunctionCode)); + body.push(...varU32(exp.funcIndex)); + } else if (exp.hasOwnProperty("memIndex")) { + body.push(...varU32(MemoryCode)); + body.push(...varU32(exp.memIndex)); + } else if (exp.hasOwnProperty("tagIndex")) { + body.push(...varU32(TagCode)); + body.push(...varU32(exp.tagIndex)); + } else { + throw "Bad export " + exp; + } + } + return { name: exportId, body }; +} + +function tableSection(initialSize) { + var body = []; + body.push(...varU32(1)); // number of tables + body.push(...varU32(AnyFuncCode)); + body.push(...varU32(0x0)); // for now, no maximum + body.push(...varU32(initialSize)); + return { name: tableId, body }; +} + +function memorySection(initialSize) { + var body = []; + body.push(...varU32(1)); // number of memories + body.push(...varU32(0x0)); // for now, no maximum + body.push(...varU32(initialSize)); + return { name: memoryId, body }; +} + +function tagSection(tags) { + var body = []; + body.push(...varU32(tags.length)); + for (let tag of tags) { + body.push(...varU32(0)); // exception attribute + body.push(...varU32(tag.type)); + } + return { name: tagId, body }; +} + +function dataSection(segmentArrays) { + var body = []; + body.push(...varU32(segmentArrays.length)); + for (let array of segmentArrays) { + body.push(...varU32(0)); // table index + body.push(...varU32(I32ConstCode)); + body.push(...varS32(array.offset)); + body.push(...varU32(EndCode)); + body.push(...varU32(array.elems.length)); + for (let elem of array.elems) + body.push(...varU32(elem)); + } + return { name: dataId, body }; +} + +function dataCountSection(count) { + var body = []; + body.push(...varU32(count)); + return { name: dataCountId, body }; +} + +function globalSection(globalArray) { + var body = []; + body.push(...varU32(globalArray.length)); + for (let globalObj of globalArray) { + // Value type + body.push(...varU32(globalObj.valType)); + // Flags + body.push(globalObj.flags & 255); + // Initializer expression + body.push(...globalObj.initExpr); + } + return { name: globalId, body }; +} + +function elemSection(elemArrays) { + var body = []; + body.push(...varU32(elemArrays.length)); + for (let array of elemArrays) { + body.push(...varU32(0)); // table index + body.push(...varU32(I32ConstCode)); + body.push(...varS32(array.offset)); + body.push(...varU32(EndCode)); + body.push(...varU32(array.elems.length)); + for (let elem of array.elems) + body.push(...varU32(elem)); + } + return { name: elemId, body }; +} + +// For now, the encoding spec is here: +// https://github.com/WebAssembly/bulk-memory-operations/issues/98#issuecomment-507330729 + +const LegacyActiveExternVal = 0; +const PassiveExternVal = 1; +const ActiveExternVal = 2; +const DeclaredExternVal = 3; +const LegacyActiveElemExpr = 4; +const PassiveElemExpr = 5; +const ActiveElemExpr = 6; +const DeclaredElemExpr = 7; + +function generalElemSection(elemObjs) { + let body = []; + body.push(...varU32(elemObjs.length)); + for (let elemObj of elemObjs) { + body.push(elemObj.flag); + if ((elemObj.flag & 3) == 2) + body.push(...varU32(elemObj.table)); + // TODO: This is not very flexible + if ((elemObj.flag & 1) == 0) { + body.push(...varU32(I32ConstCode)); + body.push(...varS32(elemObj.offset)); + body.push(...varU32(EndCode)); + } + if (elemObj.flag & 4) { + if (elemObj.flag & 3) + body.push(elemObj.typeCode & 255); + // Each element is an array of bytes + body.push(...varU32(elemObj.elems.length)); + for (let elemBytes of elemObj.elems) + body.push(...elemBytes); + } else { + if (elemObj.flag & 3) + body.push(elemObj.externKind & 255); + // Each element is a putative function index + body.push(...varU32(elemObj.elems.length)); + for (let elem of elemObj.elems) + body.push(...varU32(elem)); + } + } + return { name: elemId, body }; +} + +function moduleNameSubsection(moduleName) { + var body = []; + body.push(...varU32(nameTypeModule)); + + var subsection = encodedString(moduleName); + body.push(...varU32(subsection.length)); + body.push(...subsection); + + return body; +} + +function funcNameSubsection(funcNames) { + var body = []; + body.push(...varU32(nameTypeFunction)); + + var subsection = varU32(funcNames.length); + + var funcIndex = 0; + for (let f of funcNames) { + subsection.push(...varU32(f.index ? f.index : funcIndex)); + subsection.push(...encodedString(f.name, f.nameLen)); + funcIndex++; + } + + body.push(...varU32(subsection.length)); + body.push(...subsection); + return body; +} + +function nameSection(subsections) { + var body = []; + body.push(...string(nameName)); + + for (let ss of subsections) + body.push(...ss); + + return { name: userDefinedId, body }; +} + +function customSection(name, ...body) { + return { name: userDefinedId, body: [...string(name), ...body] }; +} + +function tableSection0() { + var body = []; + body.push(...varU32(0)); // number of tables + return { name: tableId, body }; +} + +function memorySection0() { + var body = []; + body.push(...varU32(0)); // number of memories + return { name: memoryId, body }; +} diff --git a/js/src/jit-test/lib/wasm-caching.js b/js/src/jit-test/lib/wasm-caching.js new file mode 100644 index 0000000000..fc39ab999d --- /dev/null +++ b/js/src/jit-test/lib/wasm-caching.js @@ -0,0 +1,37 @@ +const {Module, Instance, compileStreaming, RuntimeError} = WebAssembly; + +function testCached(code, imports, test) { + if (typeof code === 'string') + code = wasmTextToBinary(code); + + let success = false; + let cache = streamCacheEntry(code); + assertEq(cache.cached, false); + compileStreaming(cache) + .then(m => { + test(new Instance(m, imports)); + if (!wasmTestSerializationEnabled()) { + assertEq(wasmLoadedFromCache(m), false); + } + while (!wasmHasTier2CompilationCompleted(m)) { + sleep(1); + } + assertEq(cache.cached, true); + return compileStreaming(cache); + }) + .then(m => { + test(new Instance(m, imports)); + assertEq(wasmLoadedFromCache(m), true); + assertEq(cache.cached, true); + + let m2 = wasmCompileInSeparateProcess(code); + test(new Instance(m2, imports)); + assertEq(wasmLoadedFromCache(m2), true); + + success = true; + }) + .catch(err => { print(String(err) + " at:\n" + err.stack) }); + + drainJobQueue(); + assertEq(success, true); +} diff --git a/js/src/jit-test/lib/wasm.js b/js/src/jit-test/lib/wasm.js new file mode 100644 index 0000000000..215cf0e421 --- /dev/null +++ b/js/src/jit-test/lib/wasm.js @@ -0,0 +1,546 @@ +if (!wasmIsSupported()) + quit(); + +load(libdir + "asserts.js"); + +function canRunHugeMemoryTests() { + let conf = getBuildConfiguration(); + // We're aiming for 64-bit desktop builds with no interesting analysis + // running that might inflate memory consumption unreasonably. It's OK if + // they're debug builds, though. + // + // The build configuration object may be extended at any time with new + // properties, so neither an allowlist of properties that can be true or a + // blocklist of properties that can't be true is great. But the latter is + // probably better. + let blocked = ['rooting-analysis','simulator', + 'android','wasi','asan','tsan','ubsan','dtrace','valgrind']; + for ( let b of blocked ) { + if (conf[b]) { + print("Failing canRunHugeMemoryTests() because '" + b + "' is true"); + return false; + } + } + if (conf['pointer-byte-size'] != 8) { + print("Failing canRunHugeMemoryTests() because the build is not 64-bit"); + return false; + } + return true; +} + +// On 64-bit systems with explicit bounds checking, ion and baseline can handle +// 65536 pages. + +var PageSizeInBytes = 65536; +var MaxBytesIn32BitMemory = 0; +if (largeArrayBufferSupported()) { + MaxBytesIn32BitMemory = 65536*PageSizeInBytes; +} else { + // This is an overestimate twice: first, the max byte value is divisible by + // the page size; second, it must be a valid bounds checking immediate. But + // INT32_MAX is fine for testing. + MaxBytesIn32BitMemory = 0x7FFF_FFFF; +} +var MaxPagesIn32BitMemory = Math.floor(MaxBytesIn32BitMemory / PageSizeInBytes); + +function wasmEvalText(str, imports) { + let binary = wasmTextToBinary(str); + let valid = WebAssembly.validate(binary); + + let m; + try { + m = new WebAssembly.Module(binary); + assertEq(valid, true, "failed WebAssembly.validate but still compiled successfully"); + } catch(e) { + if (!e.toString().match(/out of memory/)) { + assertEq(valid, false, `passed WebAssembly.validate but failed to compile: ${e}`); + } + throw e; + } + + return new WebAssembly.Instance(m, imports); +} + +function wasmValidateText(str) { + let binary = wasmTextToBinary(str); + let valid = WebAssembly.validate(binary); + if (!valid) { + new WebAssembly.Module(binary); + throw new Error("module failed WebAssembly.validate but compiled successfully"); + } + assertEq(valid, true, "wasm module was invalid"); +} + +function wasmFailValidateText(str, pattern) { + let binary = wasmTextToBinary(str); + assertEq(WebAssembly.validate(binary), false, "module passed WebAssembly.validate when it should not have"); + assertErrorMessage(() => new WebAssembly.Module(binary), WebAssembly.CompileError, pattern, "module failed WebAssembly.validate but did not fail to compile as expected"); +} + +// Expected compilation failure can happen in a couple of ways: +// +// - The compiler can be available but not capable of recognizing some opcodes: +// Compilation will start, but will fail with a CompileError. This is what +// happens without --wasm-gc if opcodes enabled by --wasm-gc are used. +// +// - The compiler can be unavailable: Compilation will not start at all but will +// throw an Error. This is what happens with "--wasm-gc --wasm-compiler=X" if +// X does not support the features enabled by --wasm-gc. + +function wasmCompilationShouldFail(bin, compile_error_regex) { + try { + new WebAssembly.Module(bin); + } catch (e) { + if (e instanceof WebAssembly.CompileError) { + assertEq(compile_error_regex.test(e), true); + } else if (e instanceof Error) { + assertEq(/can't use wasm debug\/gc without baseline/.test(e), true); + } else { + throw new Error("Unexpected exception value:\n" + e); + } + } +} + +function mismatchError(actual, expect) { + var str = `(type mismatch: expression has type ${actual} but expected ${expect})|` + + `(type mismatch: expected ${expect}, found ${actual}\)`; + return RegExp(str); +} + +const emptyStackError = /(from empty stack)|(nothing on stack)/; +const unusedValuesError = /(unused values not explicitly dropped by end of block)|(values remaining on stack at end of block)/; + +function jsify(wasmVal) { + if (wasmVal === 'nan') + return NaN; + if (wasmVal === 'inf') + return Infinity; + if (wasmVal === '-inf') + return Infinity; + if (wasmVal === '-0') + return -0; + return wasmVal; +} + +function _augmentSrc(src, assertions) { + let i = 0; + let newSrc = src.substr(0, src.lastIndexOf(')')); + for (let { func, args, expected, type } of assertions) { + newSrc += ` + (func (export "assert_${i++}") (result i32) + ${ args ? args.join('\n') : '' } + call ${func}`; + + if (typeof expected !== 'undefined') { + switch (type) { + case 'f32': + newSrc += ` + i32.reinterpret/f32 + ${(function () { + if (expected == 'nan:arithmetic') { + expected = '0x7FC00000'; + return '(i32.const 0x7FC00000) i32.and'; + } + return ''; + })()} + i32.const ${expected} + i32.eq`; + break; + case 'f64': + newSrc += ` + i64.reinterpret/f64 + ${(function () { + if (expected == 'nan:arithmetic') { + expected = '0x7FF8000000000000'; + return '(i64.const 0x7FF8000000000000) i64.and'; + } + return ''; + })()} + i64.const ${expected} + i64.eq`; + break; + case 'i32': + newSrc += ` + i32.const ${expected} + i32.eq`; + break; + case 'i64': + newSrc += ` + i64.const ${expected} + i64.eq`; + break; + case 'v128': + newSrc += ` + v128.const ${expected} + i8x16.eq + i8x16.all_true`; + break; + default: + throw new Error("unexpected usage of wasmAssert"); + } + } else { + // Always true when there's no expected return value. + newSrc += "\ni32.const 1"; + } + + newSrc += ')\n'; + } + newSrc += ')'; + return newSrc; +} + +function wasmAssert(src, assertions, maybeImports = {}, exportBox = null) { + let { exports } = wasmEvalText(_augmentSrc(src, assertions), maybeImports); + if (exportBox !== null) + exportBox.exports = exports; + for (let i = 0; i < assertions.length; i++) { + let { func, expected, params } = assertions[i]; + let paramText = params ? params.join(', ') : ''; + assertEq(exports[`assert_${i}`](), 1, + `Unexpected value when running ${func}(${paramText}), expecting ${expected}.`); + } +} + +// Fully test a module: +// - ensure it validates. +// - ensure it compiles and produces the expected result. +// - ensure textToBinary(binaryToText(binary)) = binary +// Preconditions: +// - the binary module must export a function called "run". +function wasmFullPass(text, expected, maybeImports, ...args) { + let binary = wasmTextToBinary(text); + assertEq(WebAssembly.validate(binary), true, "Must validate."); + + let module = new WebAssembly.Module(binary); + let instance = new WebAssembly.Instance(module, maybeImports); + assertEq(typeof instance.exports.run, 'function', "A 'run' function must be exported."); + assertEq(instance.exports.run(...args), expected, "Initial module must return the expected result."); +} + +// Ditto, but expects a function named '$run' instead of exported with this name. +function wasmFullPassI64(text, expected, maybeImports, ...args) { + let binary = wasmTextToBinary(text); + assertEq(WebAssembly.validate(binary), true, "Must validate."); + + let augmentedSrc = _augmentSrc(text, [ { type: 'i64', func: '$run', args, expected } ]); + let augmentedBinary = wasmTextToBinary(augmentedSrc); + + let module = new WebAssembly.Module(augmentedBinary); + let instance = new WebAssembly.Instance(module, maybeImports); + assertEq(instance.exports.assert_0(), 1); +} + +function wasmRunWithDebugger(wast, lib, init, done) { + let g = newGlobal({newCompartment: true}); + let dbg = new Debugger(g); + + g.eval(` +var wasm = wasmTextToBinary(\`${wast}\`); +var lib = ${lib || 'undefined'}; +var m = new WebAssembly.Instance(new WebAssembly.Module(wasm), lib);`); + + var wasmScript = dbg.findScripts().filter(s => s.format == 'wasm')[0]; + + init({dbg, wasmScript, g,}); + let result = undefined, error = undefined; + try { + result = g.eval("m.exports.test()"); + } catch (ex) { + error = ex; + } + done({dbg, result, error, wasmScript, g,}); +} + +const WasmHelpers = {}; + +(function() { + let enabled = false; + try { + enableSingleStepProfiling(); + disableSingleStepProfiling(); + enabled = true; + } catch (e) {} + WasmHelpers.isSingleStepProfilingEnabled = enabled; +})(); + +// The cache of matched and unmatched strings seriously speeds up matching on +// the emulators and makes tests time out less often. + +var matched = {}; +var unmatched = {}; + +WasmHelpers._normalizeStack = (stack, preciseStacks) => { + var wasmFrameTypes = [ + {re:/^jit call to int64(?: or v128)? wasm function$/, sub:"i64>"}, + {re:/^out-of-line coercion for jit entry arguments \(in wasm\)$/, sub:"ool>"}, + {re:/^wasm-function\[(\d+)\] \(.*\)$/, sub:"$1"}, + {re:/^(fast|slow) exit trampoline (?:to native )?\(in wasm\)$/, sub:"<"}, + {re:/^call to(?: asm.js)? native (.*) \(in wasm\)$/, sub:"$1"}, + {re:/ \(in wasm\)$/, sub:""} + ]; + + let entryRegexps; + if (preciseStacks) { + entryRegexps = [ + {re:/^slow entry trampoline \(in wasm\)$/, sub:"!>"}, + {re:/^fast entry trampoline \(in wasm\)$/, sub:">"}, + ]; + } else { + entryRegexps = [ + {re:/^(fast|slow) entry trampoline \(in wasm\)$/, sub:">"} + ]; + } + wasmFrameTypes = entryRegexps.concat(wasmFrameTypes); + + var framesIn = stack.split(','); + var framesOut = []; + outer: + for (let frame of framesIn) { + if (unmatched[frame]) + continue; + let probe = matched[frame]; + if (probe !== undefined) { + framesOut.push(probe); + continue; + } + for (let {re, sub} of wasmFrameTypes) { + if (re.test(frame)) { + let repr = frame.replace(re, sub); + framesOut.push(repr); + matched[frame] = repr; + continue outer; + } + } + unmatched[frame] = true; + } + + return framesOut.join(','); +}; + +WasmHelpers._removeAdjacentDuplicates = array => { + if (array.length < 2) + return; + let i = 0; + for (let j = 1; j < array.length; j++) { + if (array[i] !== array[j]) + array[++i] = array[j]; + } + array.length = i + 1; +} + +WasmHelpers.normalizeStacks = (stacks, preciseStacks = false) => { + let observed = []; + for (let i = 0; i < stacks.length; i++) + observed[i] = WasmHelpers._normalizeStack(stacks[i], preciseStacks); + WasmHelpers._removeAdjacentDuplicates(observed); + return observed; +}; + +WasmHelpers._compareStacks = (got, expect) => { + if (got.length != expect.length) { + return false; + } + for (let i = 0; i < got.length; i++) { + if (got[i] !== expect[i]) + return false; + } + return true; +} + +WasmHelpers.assertEqImpreciseStacks = (got, expect) => { + let observed = WasmHelpers.normalizeStacks(got, /* precise */ false); + let same = WasmHelpers._compareStacks(observed, expect); + if (!same) { + if (observed.length != expect.length) { + print(`Got:\n${observed.toSource()}\nExpect:\n${expect.toSource()}`); + assertEq(observed.length, expect.length); + } + for (let i = 0; i < observed.length; i++) { + if (observed[i] !== expect[i]) { + print(`On stack ${i}, Got:\n${observed[i]}\nExpect:\n${expect[i]}`); + assertEq(observed[i], expect[i]); + } + } + } +} + +WasmHelpers.extractStackFrameFunction = (frameString) => { + var [_, name, filename, line, column] = frameString.match(/^(.*)@(.*):(.*):(.*)$/); + if (name) + return name; + if (/wasm-function/.test(line)) + return line; + return ""; +}; + +WasmHelpers.assertStackTrace = (exception, expected) => { + let callsites = exception.stack.trim().split('\n').map(WasmHelpers.extractStackFrameFunction); + assertEq(callsites.length, expected.length); + for (let i = 0; i < callsites.length; i++) { + assertEq(callsites[i], expected[i]); + } +}; + +WasmHelpers.nextLineNumber = (n=1) => { + return +(new Error().stack).split('\n')[1].split(':')[1] + n; +} + +WasmHelpers.startProfiling = () => { + if (!WasmHelpers.isSingleStepProfilingEnabled) + return; + enableSingleStepProfiling(); +} + +WasmHelpers.endProfiling = () => { + if (!WasmHelpers.isSingleStepProfilingEnabled) + return; + return disableSingleStepProfiling(); +} + +WasmHelpers.assertEqPreciseStacks = (observed, expectedStacks) => { + if (!WasmHelpers.isSingleStepProfilingEnabled) + return null; + + observed = WasmHelpers.normalizeStacks(observed, /* precise */ true); + + for (let i = 0; i < expectedStacks.length; i++) { + if (WasmHelpers._compareStacks(observed, expectedStacks[i])) + return i; + } + + throw new Error(`no plausible stacks found, observed: ${observed.join('/')} +Expected one of: +${expectedStacks.map(stacks => stacks.join("/")).join('\n')}`); +} + +function fuzzingSafe() { + return typeof getErrorNotes == 'undefined'; +} + +// Common instantiations of wasm values for dynamic type check testing + +// Valid values for funcref +let WasmFuncrefValues = [ + wasmEvalText(`(module (func (export "")))`).exports[''], +]; + +// Valid values for structref/arrayref +let WasmStructrefValues = []; +let WasmArrayrefValues = []; +if (wasmGcEnabled()) { + let { newStruct, newArray } = wasmEvalText(` + (module + (type $s (struct)) + (type $a (array i32)) + (func (export "newStruct") (result anyref) + struct.new $s) + (func (export "newArray") (result anyref) + i32.const 0 + i32.const 0 + array.new $a) + )`).exports; + WasmStructrefValues.push(newStruct()); + WasmArrayrefValues.push(newArray()); +} + +// Valid values for eqref +let WasmEqrefValues = [...WasmStructrefValues, ...WasmArrayrefValues]; + +// Valid and invalid values for anyref +let WasmAnyrefValues = [...WasmEqrefValues]; +let WasmNonAnyrefValues = [ + undefined, + true, + false, + {x:1337}, + ["abracadabra"], + 1337, + 13.37, + "hi", + 37n, + new Number(42), + new Boolean(true), + Symbol("status"), + () => 1337, + ...WasmFuncrefValues, +]; + +// Valid externref values +let WasmNonNullExternrefValues = [ + ...WasmNonAnyrefValues, + ...WasmAnyrefValues +]; +let WasmExternrefValues = [null, ...WasmNonNullExternrefValues]; + +// Common array utilities + +// iota(n) creates an Array of length n with values 0..n-1 +function iota(len) { + let xs = []; + for ( let i=0 ; i < len ; i++ ) + xs.push(i); + return xs; +} + +// cross(A) where A is an array of length n creates an Array length n*n of +// two-element Arrays representing all pairs of elements of A. +function cross(xs) { + let results = []; + for ( let x of xs ) + for ( let y of xs ) + results.push([x,y]); + return results; +} + +// Remove all values equal to v from an array xs, comparing equal for NaN. +function remove(v, xs) { + let result = []; + for ( let w of xs ) { + if (v === w || isNaN(v) && isNaN(w)) + continue; + result.push(w); + } + return result; +} + +// permute(A) where A is an Array returns an Array of Arrays, each inner Array a +// distinct permutation of the elements of A. A is assumed not to have any +// elements that are pairwise equal in the sense of remove(). +function permute(xs) { + if (xs.length == 1) + return [xs]; + let results = []; + for (let v of xs) + for (let tail of permute(remove(v, xs))) + results.push([v, ...tail]); + return results; +} + +// interleave([a,b,c,...],[0,1,2,...]) => [a,0,b,1,c,2,...] +function interleave(xs, ys) { + assertEq(xs.length, ys.length); + let res = []; + for ( let i=0 ; i < xs.length; i++ ) { + res.push(xs[i]); + res.push(ys[i]); + } + return res; +} + +// assertSame([a,...],[b,...]) asserts that the two arrays have the same length +// and that they element-wise assertEq IGNORING Number/BigInt differences. This +// predicate is in this file because it is wasm-specific. +function assertSame(got, expected) { + assertEq(got.length, expected.length); + for ( let i=0; i < got.length; i++ ) { + let g = got[i]; + let e = expected[i]; + if (typeof g != typeof e) { + if (typeof g == "bigint") + e = BigInt(e); + else if (typeof e == "bigint") + g = BigInt(g); + } + assertEq(g, e); + } +} diff --git a/js/src/jit-test/manual-tests/dense-to-sparse.js b/js/src/jit-test/manual-tests/dense-to-sparse.js new file mode 100644 index 0000000000..efe56620da --- /dev/null +++ b/js/src/jit-test/manual-tests/dense-to-sparse.js @@ -0,0 +1,40 @@ +// |jit-test| allow-oom +// Appending elements to a dense array should make the array sparse when the +// length exceeds the limit. + +function test() { + const MAX_DENSE_ELEMENTS_ALLOCATION = (1 << 28) - 1; + const VALUES_PER_HEADER = 2; + const MAX_DENSE_ELEMENTS_COUNT = MAX_DENSE_ELEMENTS_ALLOCATION - VALUES_PER_HEADER; + const SPARSE_DENSITY_RATIO = 8; + const MIN_DENSE = MAX_DENSE_ELEMENTS_COUNT / SPARSE_DENSITY_RATIO; + const MARGIN = 16; + + let a = []; + // Fill the beginning of array to make it keep dense until length exceeds + // MAX_DENSE_ELEMENTS_COUNT. + for (let i = 0; i < MIN_DENSE; i++) + a[i] = i; + + // Skip from MIN_DENSE to MAX_DENSE_ELEMENTS_COUNT - MARGIN, to reduce the + // time taken by test. + + // Fill the ending of array to make it sparse at MAX_DENSE_ELEMENTS_COUNT. + for (let i = MAX_DENSE_ELEMENTS_COUNT - MARGIN; i < MAX_DENSE_ELEMENTS_COUNT + MARGIN; i++) + a[i] = i; + + // Make sure the last element is defined. + assertEq(a.length, MAX_DENSE_ELEMENTS_COUNT + MARGIN); + assertEq(a[a.length - 1], MAX_DENSE_ELEMENTS_COUNT + MARGIN - 1); + + // Make sure elements around MAX_DENSE_ELEMENTS_COUNT are also defined. + assertEq(a[MAX_DENSE_ELEMENTS_COUNT - 1], MAX_DENSE_ELEMENTS_COUNT - 1); + assertEq(a[MAX_DENSE_ELEMENTS_COUNT], MAX_DENSE_ELEMENTS_COUNT); + assertEq(a[MAX_DENSE_ELEMENTS_COUNT + 1], MAX_DENSE_ELEMENTS_COUNT + 1); +} + +var config = getBuildConfiguration(); +// Takes too long time on debug build. +if (!config.debug) { + test(); +} diff --git a/js/src/jit-test/modules/ambiguous.js b/js/src/jit-test/modules/ambiguous.js new file mode 100644 index 0000000000..3e0d31e3ed --- /dev/null +++ b/js/src/jit-test/modules/ambiguous.js @@ -0,0 +1,2 @@ +export * from 'module1.js'; +export * from 'module1a.js'; diff --git a/js/src/jit-test/modules/cyclicImport1.js b/js/src/jit-test/modules/cyclicImport1.js new file mode 100644 index 0000000000..d64803a005 --- /dev/null +++ b/js/src/jit-test/modules/cyclicImport1.js @@ -0,0 +1,4 @@ +import { b } from "cyclicImport2.js"; + +export let a = 1; +assertEq(b, 2); diff --git a/js/src/jit-test/modules/cyclicImport2.js b/js/src/jit-test/modules/cyclicImport2.js new file mode 100644 index 0000000000..e7c3cd924e --- /dev/null +++ b/js/src/jit-test/modules/cyclicImport2.js @@ -0,0 +1,4 @@ +import { a } from "cyclicImport1.js"; + +export let b = 2; +assertEq(a, 1); diff --git a/js/src/jit-test/modules/defaultClass.js b/js/src/jit-test/modules/defaultClass.js new file mode 100644 index 0000000000..c05d5ff5a0 --- /dev/null +++ b/js/src/jit-test/modules/defaultClass.js @@ -0,0 +1,5 @@ +export default class { + triple(x) { + return x * 3; + } +} diff --git a/js/src/jit-test/modules/defaultFunction.js b/js/src/jit-test/modules/defaultFunction.js new file mode 100644 index 0000000000..1c6e75d453 --- /dev/null +++ b/js/src/jit-test/modules/defaultFunction.js @@ -0,0 +1,3 @@ +export default function(x) { + return x * 2; +} diff --git a/js/src/jit-test/modules/empty.js b/js/src/jit-test/modules/empty.js new file mode 100644 index 0000000000..bd9ec079d8 --- /dev/null +++ b/js/src/jit-test/modules/empty.js @@ -0,0 +1 @@ +// Intentionally empty. diff --git a/js/src/jit-test/modules/export-circular-nonexisting-binding-1.js b/js/src/jit-test/modules/export-circular-nonexisting-binding-1.js new file mode 100644 index 0000000000..2b91b6a284 --- /dev/null +++ b/js/src/jit-test/modules/export-circular-nonexisting-binding-1.js @@ -0,0 +1,4 @@ +import "export-circular-nonexisting-binding-2.js"; + +export* from "empty.js"; +export {x} from "empty.js"; diff --git a/js/src/jit-test/modules/export-circular-nonexisting-binding-2.js b/js/src/jit-test/modules/export-circular-nonexisting-binding-2.js new file mode 100644 index 0000000000..ba7dcc1b48 --- /dev/null +++ b/js/src/jit-test/modules/export-circular-nonexisting-binding-2.js @@ -0,0 +1 @@ +export {x} from "export-circular-nonexisting-binding-1.js"; diff --git a/js/src/jit-test/modules/export-default-async-asi.js b/js/src/jit-test/modules/export-default-async-asi.js new file mode 100644 index 0000000000..a69a7aa3dc --- /dev/null +++ b/js/src/jit-test/modules/export-default-async-asi.js @@ -0,0 +1,2 @@ +export default async // ASI occurs here due to the [no LineTerminator here] restriction on default-exporting an async function +function async() { return 17; } diff --git a/js/src/jit-test/modules/export-default-async-regexpy.js b/js/src/jit-test/modules/export-default-async-regexpy.js new file mode 100644 index 0000000000..3a33ecbc85 --- /dev/null +++ b/js/src/jit-test/modules/export-default-async-regexpy.js @@ -0,0 +1,6 @@ +var async = 42; +var x = 3; +var g = 2; + +export default async +/x/g; diff --git a/js/src/jit-test/modules/export-ns.js b/js/src/jit-test/modules/export-ns.js new file mode 100644 index 0000000000..0d2e499088 --- /dev/null +++ b/js/src/jit-test/modules/export-ns.js @@ -0,0 +1 @@ +export * as ns from "module1.js"; diff --git a/js/src/jit-test/modules/export-star-circular-1.js b/js/src/jit-test/modules/export-star-circular-1.js new file mode 100644 index 0000000000..9a0771b024 --- /dev/null +++ b/js/src/jit-test/modules/export-star-circular-1.js @@ -0,0 +1 @@ +export* from "export-star-circular-2.js"; diff --git a/js/src/jit-test/modules/export-star-circular-2.js b/js/src/jit-test/modules/export-star-circular-2.js new file mode 100644 index 0000000000..b273d9cefa --- /dev/null +++ b/js/src/jit-test/modules/export-star-circular-2.js @@ -0,0 +1,3 @@ +export {y as x} from "export-star-circular-1.js"; + +export var y = "pass"; diff --git a/js/src/jit-test/modules/exportImportMeta.js b/js/src/jit-test/modules/exportImportMeta.js new file mode 100644 index 0000000000..28937ade4e --- /dev/null +++ b/js/src/jit-test/modules/exportImportMeta.js @@ -0,0 +1,3 @@ +export default function() { + return import.meta; +} diff --git a/js/src/jit-test/modules/isEven.js b/js/src/jit-test/modules/isEven.js new file mode 100644 index 0000000000..242cddf402 --- /dev/null +++ b/js/src/jit-test/modules/isEven.js @@ -0,0 +1,12 @@ +import { isOdd } from "isOdd.js" + +export function isEven(x) { + if (x < 0) + throw "negative"; + if (x == 0) + return true; + return isOdd(x - 1); +} + +assertEq(isEven(4), true); +assertEq(isOdd(5), true); diff --git a/js/src/jit-test/modules/isOdd.js b/js/src/jit-test/modules/isOdd.js new file mode 100644 index 0000000000..2cada0cf93 --- /dev/null +++ b/js/src/jit-test/modules/isOdd.js @@ -0,0 +1,12 @@ +import { isEven } from "isEven.js" + +export function isOdd(x) { + if (x < 0) + throw "negative"; + if (x == 0) + return false; + return isEven(x - 1); +} + +assertEq(isEven(4), true); +assertEq(isOdd(5), true); diff --git a/js/src/jit-test/modules/module1.js b/js/src/jit-test/modules/module1.js new file mode 100644 index 0000000000..cc798ff50d --- /dev/null +++ b/js/src/jit-test/modules/module1.js @@ -0,0 +1 @@ +export const a = 1; diff --git a/js/src/jit-test/modules/module1a.js b/js/src/jit-test/modules/module1a.js new file mode 100644 index 0000000000..66d48fc1e6 --- /dev/null +++ b/js/src/jit-test/modules/module1a.js @@ -0,0 +1 @@ +export const a = 2; diff --git a/js/src/jit-test/modules/module2.js b/js/src/jit-test/modules/module2.js new file mode 100644 index 0000000000..6287b0f71c --- /dev/null +++ b/js/src/jit-test/modules/module2.js @@ -0,0 +1 @@ +export let b = 2; diff --git a/js/src/jit-test/modules/module3.js b/js/src/jit-test/modules/module3.js new file mode 100644 index 0000000000..df4cdade58 --- /dev/null +++ b/js/src/jit-test/modules/module3.js @@ -0,0 +1 @@ +export var c = 3; diff --git a/js/src/jit-test/modules/module4.js b/js/src/jit-test/modules/module4.js new file mode 100644 index 0000000000..456ffaafac --- /dev/null +++ b/js/src/jit-test/modules/module4.js @@ -0,0 +1 @@ +export default 4; diff --git a/js/src/jit-test/modules/recursiveStarExport.js b/js/src/jit-test/modules/recursiveStarExport.js new file mode 100644 index 0000000000..681c7be91b --- /dev/null +++ b/js/src/jit-test/modules/recursiveStarExport.js @@ -0,0 +1 @@ +export * from 'recursiveStarExport.js'; |