summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/lib
diff options
context:
space:
mode:
Diffstat (limited to '')
-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
33 files changed, 4188 insertions, 0 deletions
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);
+ }
+}