summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /js/src/jit-test
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.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')
-rw-r--r--js/src/jit-test/README107
-rw-r--r--js/src/jit-test/etc/generate-lookupswitch-tests.js365
-rw-r--r--js/src/jit-test/etc/generate-nosuchproperty-tests.js78
-rw-r--r--js/src/jit-test/etc/wasm/Makefile10
-rw-r--r--js/src/jit-test/etc/wasm/README.md83
-rw-r--r--js/src/jit-test/etc/wasm/generate-spectests/Cargo.lock266
-rw-r--r--js/src/jit-test/etc/wasm/generate-spectests/Cargo.toml19
-rw-r--r--js/src/jit-test/etc/wasm/generate-spectests/README.md62
-rw-r--r--js/src/jit-test/etc/wasm/generate-spectests/config-lock.toml31
-rw-r--r--js/src/jit-test/etc/wasm/generate-spectests/config.toml114
-rw-r--r--js/src/jit-test/etc/wasm/generate-spectests/src/main.rs490
-rw-r--r--js/src/jit-test/etc/wasm/generate-spectests/wast2js/Cargo.toml11
-rw-r--r--js/src/jit-test/etc/wasm/generate-spectests/wast2js/README.md3
-rw-r--r--js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/convert.rs672
-rw-r--r--js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/harness.js360
-rw-r--r--js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/lib.rs18
-rw-r--r--js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/license.js14
-rw-r--r--js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/main.rs40
-rw-r--r--js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/out.rs180
-rw-r--r--js/src/jit-test/etc/wasm/spec-tests.patch784
-rwxr-xr-xjs/src/jit-test/jit_test.py576
-rw-r--r--js/src/jit-test/lib/adhoc-multiplatform-test.js259
-rw-r--r--js/src/jit-test/lib/andTestHelper.js13
-rw-r--r--js/src/jit-test/lib/array-compare.js28
-rw-r--r--js/src/jit-test/lib/asm.js126
-rw-r--r--js/src/jit-test/lib/assert-offset-columns.js79
-rw-r--r--js/src/jit-test/lib/asserts.js87
-rw-r--r--js/src/jit-test/lib/bytecode-cache.js55
-rw-r--r--js/src/jit-test/lib/census.js161
-rw-r--r--js/src/jit-test/lib/codegen-arm64-test.js40
-rw-r--r--js/src/jit-test/lib/codegen-test-common.js53
-rw-r--r--js/src/jit-test/lib/codegen-x64-test.js184
-rw-r--r--js/src/jit-test/lib/codegen-x86-test.js84
-rw-r--r--js/src/jit-test/lib/dataview.js82
-rw-r--r--js/src/jit-test/lib/debuggerNXHelper.js90
-rw-r--r--js/src/jit-test/lib/eqArrayHelper.js21
-rw-r--r--js/src/jit-test/lib/evalInFrame.js32
-rw-r--r--js/src/jit-test/lib/immutable-prototype.js4
-rw-r--r--js/src/jit-test/lib/iteration.js30
-rw-r--r--js/src/jit-test/lib/jitopts.js70
-rw-r--r--js/src/jit-test/lib/match-debugger.js64
-rw-r--r--js/src/jit-test/lib/match.js1
-rw-r--r--js/src/jit-test/lib/math.js1
-rw-r--r--js/src/jit-test/lib/nightly-only.js23
-rw-r--r--js/src/jit-test/lib/non262.js1
-rw-r--r--js/src/jit-test/lib/orTestHelper.js13
-rw-r--r--js/src/jit-test/lib/pretenure.js80
-rw-r--r--js/src/jit-test/lib/prologue.js29
-rw-r--r--js/src/jit-test/lib/stepping.js32
-rw-r--r--js/src/jit-test/lib/string.js24
-rw-r--r--js/src/jit-test/lib/syntax.js1284
-rw-r--r--js/src/jit-test/lib/wasm-binary.js555
-rw-r--r--js/src/jit-test/lib/wasm-caching.js37
-rw-r--r--js/src/jit-test/lib/wasm.js546
-rw-r--r--js/src/jit-test/manual-tests/dense-to-sparse.js40
-rw-r--r--js/src/jit-test/modules/ambiguous.js2
-rw-r--r--js/src/jit-test/modules/cyclicImport1.js4
-rw-r--r--js/src/jit-test/modules/cyclicImport2.js4
-rw-r--r--js/src/jit-test/modules/defaultClass.js5
-rw-r--r--js/src/jit-test/modules/defaultFunction.js3
-rw-r--r--js/src/jit-test/modules/empty.js1
-rw-r--r--js/src/jit-test/modules/export-circular-nonexisting-binding-1.js4
-rw-r--r--js/src/jit-test/modules/export-circular-nonexisting-binding-2.js1
-rw-r--r--js/src/jit-test/modules/export-default-async-asi.js2
-rw-r--r--js/src/jit-test/modules/export-default-async-regexpy.js6
-rw-r--r--js/src/jit-test/modules/export-ns.js1
-rw-r--r--js/src/jit-test/modules/export-star-circular-1.js1
-rw-r--r--js/src/jit-test/modules/export-star-circular-2.js3
-rw-r--r--js/src/jit-test/modules/exportImportMeta.js3
-rw-r--r--js/src/jit-test/modules/isEven.js12
-rw-r--r--js/src/jit-test/modules/isOdd.js12
-rw-r--r--js/src/jit-test/modules/module1.js1
-rw-r--r--js/src/jit-test/modules/module1a.js1
-rw-r--r--js/src/jit-test/modules/module2.js1
-rw-r--r--js/src/jit-test/modules/module3.js1
-rw-r--r--js/src/jit-test/modules/module4.js1
-rw-r--r--js/src/jit-test/modules/recursiveStarExport.js1
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';