summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm/spec.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit-test/tests/wasm/spec.js')
-rw-r--r--js/src/jit-test/tests/wasm/spec.js555
1 files changed, 555 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/wasm/spec.js b/js/src/jit-test/tests/wasm/spec.js
new file mode 100644
index 000000000..e8e965fcc
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/spec.js
@@ -0,0 +1,555 @@
+load(libdir + "wasm.js");
+
+// This is meant to be a small and dumb interpreter for wast files. Either it
+// is imported by another script, which needs to define an array of arguments
+// called importedArgs, or args need to be passed to the command line.
+//
+// Possible arguments include:
+// -d enable debug verbose mode
+// -c computes line numbers in wast files (disabled by default as it
+// slows down the parser a lot)
+// -s soft fail mode: if a test fails, don't abort but continue to
+// the next one.
+// * anything else is considered a relative path to the wast file to
+// load and run. The path is relative to to the runner script,
+// i.e. this file..
+//
+// If there are no arguments, the wast interpreter will run sanity checks
+// (testing that NaN payload comparisons works, e.g.).
+
+if (typeof assert === 'undefined') {
+ var assert = function(c, msg) {
+ assertEq(c, true, msg);
+ };
+}
+
+// Element list or string.
+function Element(str, dollared, quoted) {
+ this.list = [];
+ this.str = str === undefined ? null : str;
+ this.dollared = !!dollared;
+ this.quoted = !!quoted;
+}
+
+Element.prototype.toString = function() {
+ if (this.str !== null) {
+ if (this.dollared) {
+ return "$" + this.str;
+ }
+ if (this.quoted) {
+ return `"${this.str}"`;
+ }
+ return this.str;
+ }
+ return `(${this.list.map(x => x.toString()).join(" ")})`;
+};
+
+setJitCompilerOption('wasm.test-mode', 1);
+
+// Creates a tree of s-expressions. Ported from Binaryen's SExpressionParser.
+function parseSExpression(text) {
+ var pos = 0;
+
+ function isSpace(c) {
+ switch (c) {
+ case '\n':
+ case ' ':
+ case '\r':
+ case '\t':
+ case '\v':
+ case '\f':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ function skip() {
+ while (true) {
+ let prevPos = pos;
+
+ if (pos + 2 < text.length) {
+
+ // Block comments.
+ if (text[pos] === '(' && text[pos + 1] === ';')
+ {
+ pos += 2;
+ let blockDepth = 1;
+ while (pos + 2 < text.length) {
+ if (text[pos] === '(' && text[pos + 1] === ';') {
+ pos += 2;
+ blockDepth++;
+ } else if (text[pos] === ';' && text[pos + 1] === ')') {
+ pos += 2;
+ if (!--blockDepth)
+ break;
+ } else {
+ pos++;
+ }
+ }
+ }
+
+ // Inline comments.
+ if (text[pos] === ';' && text[pos + 1] === ';') {
+ pos += 2;
+ while (text[pos] !== '\n')
+ pos++;
+ }
+ }
+
+ // Whitespaces.
+ while (isSpace(text[pos])) {
+ pos++;
+ }
+
+ if (pos === prevPos)
+ break;
+ }
+ }
+
+ function parse() {
+ skip();
+
+ if (text.length === pos || text[pos] === ')')
+ return null;
+
+ if (text[pos] === '(') {
+ pos++;
+ var ret = parseInParens();
+ skip();
+ assert(text[pos] === ')', 'inner list ends with a )');
+ pos++;
+ return ret;
+ }
+
+ return parseString();
+ }
+
+ function parseInParens() {
+ skip();
+ var start = pos;
+ var ret = new Element();
+ while (true) {
+ var curr = parse();
+ if (!curr) {
+ ret.lineno = countLines(text, pos);
+ return ret;
+ }
+ ret.list.push(curr);
+ }
+ }
+
+ function parseString() {
+ var dollared = false;
+ var quoted = false;
+ if (text[pos] === '$') {
+ pos++;
+ dollared = true;
+ }
+
+ var start = pos;
+ if (text[pos] === '"') {
+ quoted = true;
+ // Parse escaping \", but leave code escaped - we'll handle escaping in memory segments specifically.
+ pos++;
+ var str = "";
+ while (true) {
+ if (text[pos] === '"') break;
+ if (text[pos] === '\\') {
+ str += text[pos];
+ str += text[pos + 1];
+ pos += 2;
+ continue;
+ }
+ str += text[pos];
+ pos++;
+ }
+ pos++;
+ return new Element(str, dollared, quoted);
+ }
+
+ while (pos < text.length &&
+ !isSpace(text[pos]) &&
+ text[pos] != ';' &&
+ text[pos] != ')' &&
+ text[pos] != '(') {
+ pos++;
+ }
+
+ return new Element(text.substring(start, pos), dollared);
+ }
+
+ var root = null;
+ while (!root) { // Keep parsing until we pass an initial comment.
+ root = parseInParens();
+ }
+ return root;
+}
+
+var imports = {
+ spectest: {
+ print,
+ global: 666,
+ table: new WebAssembly.Table({ initial: 10, maximum: 20, element: "anyfunc" }),
+ memory: new WebAssembly.Memory({ initial: 1, maximum: 2 }),
+ }
+};
+
+function handleNonStandard(exprName, e)
+{
+ if (exprName === 'quit') {
+ quit();
+ }
+ if (exprName === 'print') {
+ print.apply(null, e.list.slice(1).map(exec))
+ return true;
+ }
+ return false;
+}
+
+function testNaNEqualityFunction() {
+ // Test NaN equality functions.
+ let u8 = new Uint8Array(16);
+ let i32 = new Int32Array(u8.buffer);
+ let f64 = new Float64Array(u8.buffer);
+ let f32 = new Float32Array(u8.buffer);
+
+ // F64 NaN
+ let someNaN = wasmEvalText('(module (func (result f64) (f64.const -nan:0x12345678)) (export "" 0))').exports[""]();
+ i32[0] = someNaN.nan_low;
+ i32[1] = someNaN.nan_high;
+ assert(Number.isNaN(f64[0]), "we've stored a f64 NaN");
+
+ assertEq(u8[0], 0x78);
+ assertEq(u8[1], 0x56);
+ assertEq(u8[2], 0x34);
+ assertEq(u8[3], 0x12);
+
+ assertEqNaN(someNaN, someNaN);
+
+ // F32 NaN
+ someNaN = wasmEvalText('(module (func (result f32) (f32.const -nan:0x123456)) (export "" 0))').exports[""]();
+ i32[0] = someNaN.nan_low;
+ assert(Number.isNaN(f32[0]), "we've stored a f32 NaN");
+
+ assertEq(u8[0], 0x56);
+ assertEq(u8[1], 0x34);
+ assertEq(u8[2] & 0x7f, 0x12);
+
+ assertEqNaN(someNaN, someNaN);
+
+ // Compare a NaN value against another one.
+ let pNaN = wasmEvalText('(module (func (result f64) (f64.const nan)) (export "" 0))').exports[""]();
+ let nNaN = wasmEvalText('(module (func (result f64) (f64.const -nan)) (export "" 0))').exports[""]();
+
+ i32[0] = pNaN.nan_low;
+ i32[1] = pNaN.nan_high;
+ i32[2] = nNaN.nan_low;
+ i32[3] = nNaN.nan_high;
+
+ assertEq(f64[0], f64[1]);
+ assertErrorMessage(() => assertEqNaN(pNaN, nNaN), Error, /Assertion failed/);
+ assertEqNaN(pNaN, pNaN);
+ assertEqNaN(nNaN, nNaN);
+}
+
+var constantCache = new Map;
+var moduleCache = new Map;
+
+function getModuleAndField(e) {
+ let nextArgIndex = 1;
+ let nameExpr = e.list[nextArgIndex];
+ let name = nameExpr.str;
+
+ let moduleName = '__last_module__';
+ if (nameExpr.dollared && !nameExpr.quoted) {
+ moduleName = name;
+ nextArgIndex += 1;
+ }
+
+ if (!moduleCache.has(moduleName)) {
+ throw new Error('We should have a module here before trying to invoke things!');
+ }
+
+ let module = moduleCache.get(moduleName);
+ let fieldName = e.list[nextArgIndex++].str;
+ let rest = e.list.slice(nextArgIndex).map(exec);
+
+ return [module, fieldName, rest];
+}
+
+// Recursively execute the expression.
+function exec(e) {
+ var exprName = e.list[0].str;
+
+ if (exprName === "module") {
+ let moduleText = e.toString();
+
+ let moduleName = null;
+ if (e.list && e.list.length >= 2 && e.list[1].str && e.list[1].dollared) {
+ moduleName = e.list[1].str;
+ moduleText = moduleText.replace(`$${moduleName}`, '');
+ }
+
+ let module = wasmEvalText(moduleText, imports).exports;
+ moduleCache.set('__last_module__', module);
+ if (moduleName) {
+ moduleCache.set(moduleName, module);
+ }
+
+ return;
+ }
+
+ if (exprName === "register") {
+ // (register IMPORT_NAME MODULE_NAME?)
+ assert(e.list[1].quoted, "first arg to register is quoted");
+ let importName = e.list[1].str;
+
+ let moduleName = '__last_module__';
+ if (e.list.length > 2) {
+ moduleName = e.list[2].str;
+ }
+
+ if (!moduleCache.has(moduleName)) {
+ throw new Error("can't register an unknown module for imports");
+ }
+
+ let module = moduleCache.get(moduleName);
+
+ imports[importName] = {};
+
+ for (let [k, v] of Object.entries(module)) {
+ imports[importName][k] = v;
+ }
+
+ return;
+ }
+
+ if (exprName === "invoke") {
+ let [module, field, args] = getModuleAndField(e);
+
+ let fn = null;
+ if (typeof module[field] === "function") {
+ fn = module[field];
+ } else {
+ throw new Error("Exported function not found: " + e);
+ }
+
+ return fn.apply(null, args);
+ }
+
+ if (exprName === "get") {
+ let [module, field, args] = getModuleAndField(e);
+ return module[field];
+ }
+
+ if (exprName.indexOf(".const") > 0) {
+ // Eval the expression using a wasm module.
+ var type = exprName.substring(0, exprName.indexOf(".const"));
+ var key = e.toString();
+
+ if (constantCache.has(key)) {
+ return constantCache.get(key);
+ }
+
+ var val = wasmEvalText(`(module (func (result ${type}) ${e}) (export "" 0))`).exports[""]();
+ constantCache.set(key, val);
+ return val;
+ }
+
+ if (exprName === "assert_return") {
+ let lhs = exec(e.list[1]);
+ // There might be a value to test against.
+ if (e.list[2]) {
+ let rhs = exec(e.list[2]);
+ if (typeof lhs === 'number') {
+ assertEq(lhs, rhs);
+ } else if (typeof lhs.nan_low === 'number') {
+ assertEqNaN(lhs, rhs);
+ } else {
+ // Int64 are emulated with objects with shape:
+ // {low: Number, high: Number}
+ assert(typeof lhs.low === 'number', 'assert_return expects NaN, int64 or number');
+ assert(typeof lhs.high === 'number', 'assert_return expects NaN, int64 or number');
+ assertEq(lhs.low, rhs.low);
+ assertEq(lhs.high, rhs.high);
+ }
+ }
+ return;
+ }
+
+ if (exprName === "assert_return_nan") {
+ let res = exec(e.list[1]);
+ if (typeof res === 'number') {
+ assertEq(res, NaN);
+ } else {
+ assert(typeof res.nan_low === 'number',
+ "assert_return_nan expects either a NaN number or a NaN custom object");
+
+ let f64 = new Float64Array(1);
+ let f32 = new Float32Array(f64.buffer);
+ let i32 = new Int32Array(f64.buffer);
+
+ i32[0] = res.nan_low;
+ i32[1] = res.nan_high;
+ assert(Number.isNaN(f64[0]) || Number.isNaN(f32[0]), "assert_return_nan test failed.");
+ }
+ return;
+ }
+
+ if (exprName === "assert_invalid" || exprName === "assert_malformed") {
+ let moduleText = e.list[1].toString();
+ let errMsg = e.list[2];
+ if (errMsg) {
+ assert(errMsg.quoted, "assert_invalid/malformed second argument must be a string");
+ errMsg.quoted = false;
+ }
+
+ // assert_invalid tests both the decoder *and* the parser itself.
+ let text;
+ try {
+ text = wasmTextToBinary(moduleText);
+ } catch(e) {
+ if (/wasm text error/.test(e.toString()))
+ return;
+ }
+
+ assertEq(WebAssembly.validate(text), false, "assert_invalid failed");
+
+ let caught = false;
+ try {
+ new WebAssembly.Module(text)
+ } catch (e) {
+ caught = true;
+ debug("Caught", e.toString(), ", expected:", errMsg);
+ }
+ assertEq(caught, true);
+ return;
+ }
+
+ if (exprName === "assert_soft_invalid") {
+ let moduleText = e.list[1].toString();
+ let errMsg = e.list[2];
+ if (errMsg) {
+ assert(errMsg.quoted, "assert_soft_invalid second argument must be a string");
+ errMsg.quoted = false;
+ }
+
+ try {
+ new WebAssembly.Module(wasmTextToBinary(moduleText));
+ } catch(e) {
+ debug('assert_soft_invalid caught:\nExpected:', errMsg, '\nActual:', e.toString());
+ }
+
+ return;
+ }
+
+ if (exprName === 'assert_trap') {
+ let caught = false;
+ let errMsg = e.list[2];
+ assert(errMsg.quoted, "assert_trap second argument must be a string");
+ errMsg.quoted = false;
+ try {
+ exec(e.list[1]);
+ } catch(err) {
+ caught = true;
+ if (err.toString().indexOf(errMsg) === -1)
+ warn(`expected error message "${errMsg}", got "${err}"`);
+ }
+ assert(caught, "assert_trap exception not caught");
+ return;
+ }
+
+ if (exprName === 'assert_unlinkable') {
+ let moduleText = e.list[1].toString();
+ let errMsg = e.list[2];
+ if (errMsg) {
+ assert(errMsg.quoted, "assert_invalid second argument must be a string");
+ errMsg.quoted = false;
+ }
+ let module = new WebAssembly.Module(wasmTextToBinary(moduleText));
+ let caught = false;
+ try {
+ new WebAssembly.Instance(module, imports);
+ } catch(err) {
+ caught = true;
+ if (err.toString().indexOf(errMsg) === -1)
+ warn(`expected error message "${errMsg}", got "${err}"`);
+ }
+ assert(caught, "assert_unlinkable exception not caught");
+ return;
+ }
+
+ if (!handleNonStandard(exprName, e)) {
+ assert(false, "NYI: " + e);
+ }
+}
+
+var args = typeof importedArgs !== 'undefined' ? importedArgs : scriptArgs;
+
+// Whether we should keep on executing tests if one of them failed or throw.
+var softFail = false;
+
+// Debug function
+var debug = function() {};
+var debugImpl = print;
+
+var warn = print;
+
+// Count number of lines from start to `input` in `text.
+var countLines = function() { return -1; }
+var countLinesImpl = function(text, input) {
+ return text.substring(0, input).split('\n').length;
+}
+
+// Specific tests to be run
+var targets = [];
+for (let arg of args) {
+ switch (arg) {
+ case '-c':
+ countLines = countLinesImpl;
+ break;
+ case '-d':
+ debug = debugImpl;
+ break;
+ case '-s':
+ softFail = true;
+ break;
+ default:
+ targets.push(arg);
+ break;
+ }
+}
+
+if (!args.length) {
+ testNaNEqualityFunction();
+}
+
+top_loop:
+for (var test of targets) {
+ module = null;
+
+ debug(`Running test ${test}...`);
+
+ let source = read(scriptdir + test);
+
+ let root = new parseSExpression(source);
+
+ let success = true;
+ for (let e of root.list) {
+ try {
+ exec(e);
+ } catch(err) {
+ success = false;
+ debug(`Error in ${test}:${e.lineno}: ${err.stack ? err.stack : ''}\n${err}`);
+ if (!softFail) {
+ throw err;
+ }
+ }
+ }
+
+ if (success)
+ debug(`\n${test} PASSED`);
+ else
+ debug(`\n${test} FAILED`);
+}