diff options
Diffstat (limited to '')
59 files changed, 2999 insertions, 166 deletions
diff --git a/js/src/jit-test/jit_test.py b/js/src/jit-test/jit_test.py index d9f08a7e15..4a5a0b96da 100755 --- a/js/src/jit-test/jit_test.py +++ b/js/src/jit-test/jit_test.py @@ -368,6 +368,18 @@ def main(argv): op.add_argument( "-z", "--gc-zeal", help="GC zeal mode to use when running the shell" ) + op.add_argument( + "--show-slow", + action="store_true", + help="Show tests taking longer than a minimum time (in seconds).", + ) + op.add_argument( + "--slow-test-threshold", + type=float, + default=5.0, + help="Time in seconds a test can take until it is considered slow " + "(default %(default)s).", + ) options, test_args = op.parse_known_args(argv) js_shell = which(options.js_shell) diff --git a/js/src/jit-test/tests/asm.js/bug1174372.js b/js/src/jit-test/tests/asm.js/bug1174372.js index e7031ea104..d48d2a0533 100644 --- a/js/src/jit-test/tests/asm.js/bug1174372.js +++ b/js/src/jit-test/tests/asm.js/bug1174372.js @@ -1,4 +1,4 @@ -// |jit-test| --no-baseline; --non-writable-jitcode +// |jit-test| --no-baseline (function(stdlib, foreign, heap) { "use asm"; function f() {} diff --git a/js/src/jit-test/tests/basic/bug1892300.js b/js/src/jit-test/tests/basic/bug1892300.js new file mode 100644 index 0000000000..e4c2b1f5b7 --- /dev/null +++ b/js/src/jit-test/tests/basic/bug1892300.js @@ -0,0 +1,4 @@ +newGlobal().eval(`(function () { + enableShellAllocationMetadataBuilder(); + return arguments[Symbol.iterator]; +})();`); diff --git a/js/src/jit-test/tests/basic/bug1894883.js b/js/src/jit-test/tests/basic/bug1894883.js new file mode 100644 index 0000000000..74312c9721 --- /dev/null +++ b/js/src/jit-test/tests/basic/bug1894883.js @@ -0,0 +1,8 @@ +function makeObj() { return { x: 1, y: 2 }; } + +var o1 = makeObj(); +var o2 = makeObj(); + +var snapshot = createShapeSnapshot(o1); +delete o1.x; +checkShapeSnapshot(snapshot, o2); diff --git a/js/src/jit-test/tests/basic/shell-flags-fuzzing.js b/js/src/jit-test/tests/basic/shell-flags-fuzzing.js new file mode 100644 index 0000000000..e6be22cf5a --- /dev/null +++ b/js/src/jit-test/tests/basic/shell-flags-fuzzing.js @@ -0,0 +1,3 @@ +// |jit-test| --fuzzing-safe; --enable-foobarbaz; error:987 +// If --fuzzing-safe is used, unknown shell flags that follow it are ignored. +throw 987; diff --git a/js/src/jit-test/tests/basic/shell-prefs-fuzzing.js b/js/src/jit-test/tests/basic/shell-prefs-fuzzing.js new file mode 100644 index 0000000000..29d3acb347 --- /dev/null +++ b/js/src/jit-test/tests/basic/shell-prefs-fuzzing.js @@ -0,0 +1,4 @@ +// |jit-test| --fuzzing-safe; --setpref=foobar=123; error:987 +// If --fuzzing-safe is used, unknown pref names are ignored, similar to unknown +// shell flags. +throw 987; diff --git a/js/src/jit-test/tests/basic/shell-prefs-no-fuzzing.js b/js/src/jit-test/tests/basic/shell-prefs-no-fuzzing.js new file mode 100644 index 0000000000..a9aa4d5d77 --- /dev/null +++ b/js/src/jit-test/tests/basic/shell-prefs-no-fuzzing.js @@ -0,0 +1,2 @@ +// |jit-test| --setpref=foobar=123; exitstatus: 1 +// If --fuzzing-safe is not used, unknown pref names are reported as an error. diff --git a/js/src/jit-test/tests/basic/testTypeofEq.js b/js/src/jit-test/tests/basic/testTypeofEq.js new file mode 100644 index 0000000000..cf42a7c6c9 --- /dev/null +++ b/js/src/jit-test/tests/basic/testTypeofEq.js @@ -0,0 +1,492 @@ +var testcases = [ + [1, "NUMBER"], + [1.1, "NUMBER"], + [true, "BOOLEAN"], + [false, "BOOLEAN"], + [null, "OBJECT"], + [undefined, "UNDEFINED"], + ["foo", "STRING"], + [Symbol.iterator, "SYMBOL"], + [function f() {}, "FUNCTION"], + [{}, "OBJECT"], +]; + +function runTest(f) { + for (let i = 0; i < 2000; i++) { + let testcase = testcases[i % testcases.length]; + assertEq(f(testcase[0]), testcase[1]); + } +} + +function loose_left(x) { + if (typeof x == "boolean") { + return "BOOLEAN"; + } + if (typeof x == "function") { + return "FUNCTION"; + } + if (typeof x == "number") { + return "NUMBER"; + } + if (typeof x == "object") { + return "OBJECT"; + } + if (typeof x == "string") { + return "STRING"; + } + if (typeof x == "symbol") { + return "SYMBOL"; + } + if (typeof x == "undefined") { + return "UNDEFINED"; + } + return "???"; +} + +function loose_right(x) { + if ("boolean" == typeof x) { + return "BOOLEAN"; + } + if ("function" == typeof x) { + return "FUNCTION"; + } + if ("number" == typeof x) { + return "NUMBER"; + } + if ("object" == typeof x) { + return "OBJECT"; + } + if ("string" == typeof x) { + return "STRING"; + } + if ("symbol" == typeof x) { + return "SYMBOL"; + } + if ("undefined" == typeof x) { + return "UNDEFINED"; + } + return "???"; +} + +function strict_left(x) { + if (typeof x === "boolean") { + return "BOOLEAN"; + } + if (typeof x === "function") { + return "FUNCTION"; + } + if (typeof x === "number") { + return "NUMBER"; + } + if (typeof x === "object") { + return "OBJECT"; + } + if (typeof x === "string") { + return "STRING"; + } + if (typeof x === "symbol") { + return "SYMBOL"; + } + if (typeof x === "undefined") { + return "UNDEFINED"; + } + return "???"; +} + +function strict_right(x) { + if ("boolean" === typeof x) { + return "BOOLEAN"; + } + if ("function" === typeof x) { + return "FUNCTION"; + } + if ("number" === typeof x) { + return "NUMBER"; + } + if ("object" === typeof x) { + return "OBJECT"; + } + if ("string" === typeof x) { + return "STRING"; + } + if ("symbol" === typeof x) { + return "SYMBOL"; + } + if ("undefined" === typeof x) { + return "UNDEFINED"; + } + return "???"; +} + +function loose_left_ne(x) { + if (typeof x != "boolean") { + } else { + return "BOOLEAN"; + } + if (typeof x != "function") { + } else { + return "FUNCTION"; + } + if (typeof x != "number") { + } else { + return "NUMBER"; + } + if (typeof x != "object") { + } else { + return "OBJECT"; + } + if (typeof x != "string") { + } else { + return "STRING"; + } + if (typeof x != "symbol") { + } else { + return "SYMBOL"; + } + if (typeof x != "undefined") { + } else { + return "UNDEFINED"; + } + return "???"; +} + +function loose_right_ne(x) { + if ("boolean" != typeof x) { + } else { + return "BOOLEAN"; + } + if ("function" != typeof x) { + } else { + return "FUNCTION"; + } + if ("number" != typeof x) { + } else { + return "NUMBER"; + } + if ("object" != typeof x) { + } else { + return "OBJECT"; + } + if ("string" != typeof x) { + } else { + return "STRING"; + } + if ("symbol" != typeof x) { + } else { + return "SYMBOL"; + } + if ("undefined" != typeof x) { + } else { + return "UNDEFINED"; + } + return "???"; +} + +function strict_left_ne(x) { + if (typeof x !== "boolean") { + } else { + return "BOOLEAN"; + } + if (typeof x !== "function") { + } else { + return "FUNCTION"; + } + if (typeof x !== "number") { + } else { + return "NUMBER"; + } + if (typeof x !== "object") { + } else { + return "OBJECT"; + } + if (typeof x !== "string") { + } else { + return "STRING"; + } + if (typeof x !== "symbol") { + } else { + return "SYMBOL"; + } + if (typeof x !== "undefined") { + } else { + return "UNDEFINED"; + } + return "???"; +} + +function strict_right_ne(x) { + if ("boolean" !== typeof x) { + } else { + return "BOOLEAN"; + } + if ("function" !== typeof x) { + } else { + return "FUNCTION"; + } + if ("number" !== typeof x) { + } else { + return "NUMBER"; + } + if ("object" !== typeof x) { + } else { + return "OBJECT"; + } + if ("string" !== typeof x) { + } else { + return "STRING"; + } + if ("symbol" !== typeof x) { + } else { + return "SYMBOL"; + } + if ("undefined" !== typeof x) { + } else { + return "UNDEFINED"; + } + return "???"; +} + +function loose_left_expr(x) { + if (typeof (0, x) == "boolean") { + return "BOOLEAN"; + } + if (typeof (0, x) == "function") { + return "FUNCTION"; + } + if (typeof (0, x) == "number") { + return "NUMBER"; + } + if (typeof (0, x) == "object") { + return "OBJECT"; + } + if (typeof (0, x) == "string") { + return "STRING"; + } + if (typeof (0, x) == "symbol") { + return "SYMBOL"; + } + if (typeof (0, x) == "undefined") { + return "UNDEFINED"; + } + return "???"; +} + +function loose_right_expr(x) { + if ("boolean" == typeof (0, x)) { + return "BOOLEAN"; + } + if ("function" == typeof (0, x)) { + return "FUNCTION"; + } + if ("number" == typeof (0, x)) { + return "NUMBER"; + } + if ("object" == typeof (0, x)) { + return "OBJECT"; + } + if ("string" == typeof (0, x)) { + return "STRING"; + } + if ("symbol" == typeof (0, x)) { + return "SYMBOL"; + } + if ("undefined" == typeof (0, x)) { + return "UNDEFINED"; + } + return "???"; +} + +function strict_left_expr(x) { + if (typeof (0, x) === "boolean") { + return "BOOLEAN"; + } + if (typeof (0, x) === "function") { + return "FUNCTION"; + } + if (typeof (0, x) === "number") { + return "NUMBER"; + } + if (typeof (0, x) === "object") { + return "OBJECT"; + } + if (typeof (0, x) === "string") { + return "STRING"; + } + if (typeof (0, x) === "symbol") { + return "SYMBOL"; + } + if (typeof (0, x) === "undefined") { + return "UNDEFINED"; + } + return "???"; +} + +function strict_right_expr(x) { + if ("boolean" === typeof (0, x)) { + return "BOOLEAN"; + } + if ("function" === typeof (0, x)) { + return "FUNCTION"; + } + if ("number" === typeof (0, x)) { + return "NUMBER"; + } + if ("object" === typeof (0, x)) { + return "OBJECT"; + } + if ("string" === typeof (0, x)) { + return "STRING"; + } + if ("symbol" === typeof (0, x)) { + return "SYMBOL"; + } + if ("undefined" === typeof (0, x)) { + return "UNDEFINED"; + } + return "???"; +} + +function loose_left_ne_expr(x) { + if (typeof (0, x) != "boolean") { + } else { + return "BOOLEAN"; + } + if (typeof (0, x) != "function") { + } else { + return "FUNCTION"; + } + if (typeof (0, x) != "number") { + } else { + return "NUMBER"; + } + if (typeof (0, x) != "object") { + } else { + return "OBJECT"; + } + if (typeof (0, x) != "string") { + } else { + return "STRING"; + } + if (typeof (0, x) != "symbol") { + } else { + return "SYMBOL"; + } + if (typeof (0, x) != "undefined") { + } else { + return "UNDEFINED"; + } + return "???"; +} + +function loose_right_ne_expr(x) { + if ("boolean" != typeof (0, x)) { + } else { + return "BOOLEAN"; + } + if ("function" != typeof (0, x)) { + } else { + return "FUNCTION"; + } + if ("number" != typeof (0, x)) { + } else { + return "NUMBER"; + } + if ("object" != typeof (0, x)) { + } else { + return "OBJECT"; + } + if ("string" != typeof (0, x)) { + } else { + return "STRING"; + } + if ("symbol" != typeof (0, x)) { + } else { + return "SYMBOL"; + } + if ("undefined" != typeof (0, x)) { + } else { + return "UNDEFINED"; + } + return "???"; +} + +function strict_left_ne_expr(x) { + if (typeof (0, x) !== "boolean") { + } else { + return "BOOLEAN"; + } + if (typeof (0, x) !== "function") { + } else { + return "FUNCTION"; + } + if (typeof (0, x) !== "number") { + } else { + return "NUMBER"; + } + if (typeof (0, x) !== "object") { + } else { + return "OBJECT"; + } + if (typeof (0, x) !== "string") { + } else { + return "STRING"; + } + if (typeof (0, x) !== "symbol") { + } else { + return "SYMBOL"; + } + if (typeof (0, x) !== "undefined") { + } else { + return "UNDEFINED"; + } + return "???"; +} + +function strict_right_ne_expr(x) { + if ("boolean" !== typeof (0, x)) { + } else { + return "BOOLEAN"; + } + if ("function" !== typeof (0, x)) { + } else { + return "FUNCTION"; + } + if ("number" !== typeof (0, x)) { + } else { + return "NUMBER"; + } + if ("object" !== typeof (0, x)) { + } else { + return "OBJECT"; + } + if ("string" !== typeof (0, x)) { + } else { + return "STRING"; + } + if ("symbol" !== typeof (0, x)) { + } else { + return "SYMBOL"; + } + if ("undefined" !== typeof (0, x)) { + } else { + return "UNDEFINED"; + } + return "???"; +} + +runTest(loose_left); +runTest(loose_right); +runTest(strict_left); +runTest(strict_right); +runTest(loose_left_ne); +runTest(loose_right_ne); +runTest(strict_left_ne); +runTest(strict_right_ne); +runTest(loose_left_expr); +runTest(loose_right_expr); +runTest(strict_left_expr); +runTest(strict_right_expr); +runTest(loose_left_ne_expr); +runTest(loose_right_ne_expr); +runTest(strict_left_ne_expr); +runTest(strict_right_ne_expr); diff --git a/js/src/jit-test/tests/bug1894604.js b/js/src/jit-test/tests/bug1894604.js new file mode 100644 index 0000000000..5c14789eef --- /dev/null +++ b/js/src/jit-test/tests/bug1894604.js @@ -0,0 +1,12 @@ +class C { + set f(val) { + this.f = val; + super.g++; + } +} + +let c = new C(); +gczeal(14,50); +try { + c.f = 1; +} catch {} diff --git a/js/src/jit-test/tests/debug/Debugger-dead-global.js b/js/src/jit-test/tests/debug/Debugger-dead-global.js new file mode 100644 index 0000000000..abcb370be0 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-dead-global.js @@ -0,0 +1,26 @@ +var g1 = newGlobal({newCompartment: true}); + +const dbg = new Debugger(); + +function assertThrowsDeadWrapper(f) { + let caught = false; + try { + f(); + } catch (e) { + assertEq(e.message, "can't access dead object"); + caught = true; + } + assertEq(caught, true); +} + +nukeAllCCWs(); + +// Debugger methods should throw explicit error for dead global object. +assertThrowsDeadWrapper(() => dbg.addDebuggee(g1)); +assertThrowsDeadWrapper(() => dbg.removeDebuggee(g1)); +assertThrowsDeadWrapper(() => dbg.findScripts({global: g1})); +assertThrowsDeadWrapper(() => dbg.makeGlobalObjectReference(g1)); +assertThrowsDeadWrapper(() => dbg.enableAsyncStack(g1)); +assertThrowsDeadWrapper(() => dbg.disableAsyncStack(g1)); +assertThrowsDeadWrapper(() => dbg.enableUnlimitedStacksCapturing(g1)); +assertThrowsDeadWrapper(() => dbg.disableUnlimitedStacksCapturing(g1)); diff --git a/js/src/jit-test/tests/debug/Debugger-shouldAvoidSideEffects.js b/js/src/jit-test/tests/debug/Debugger-shouldAvoidSideEffects.js new file mode 100644 index 0000000000..62d6693d4e --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-shouldAvoidSideEffects.js @@ -0,0 +1,57 @@ +// Test shouldAvoidSideEffects flag. + +const g = newGlobal({newCompartment: true}); +const dbg = Debugger(g); +const gdbg = dbg.addDebuggee(g); + +gdbg.executeInGlobal(` +var obj, result, reachedNextLine; +`); + +dbg.shouldAvoidSideEffects = false; +assertEq(dbg.shouldAvoidSideEffects, false); + +let result = gdbg.executeInGlobal(` +result = undefined; +reachedNextLine = false; + +obj = createSideEffectfulResolveObject(); +result = obj.test; +reachedNextLine = true; +"finished"; +`); +assertEq(g.result, 42); +assertEq(g.reachedNextLine, true); +assertEq(result.return, "finished"); + +dbg.shouldAvoidSideEffects = true; +assertEq(dbg.shouldAvoidSideEffects, true); + +result = gdbg.executeInGlobal(` +result = undefined; +reachedNextLine = false; + +obj = createSideEffectfulResolveObject(); +result = obj.test; +reachedNextLine = true; +"finished"; +`); +assertEq(g.result, undefined); +assertEq(g.reachedNextLine, false); +assertEq(result, null); + +// Resolve after abort. +dbg.shouldAvoidSideEffects = false; +assertEq(dbg.shouldAvoidSideEffects, false); + +result = gdbg.executeInGlobal(` +result = undefined; +reachedNextLine = false; + +result = obj.test; +reachedNextLine = true; +"finished"; +`); +assertEq(g.result, 42); +assertEq(g.reachedNextLine, true); +assertEq(result.return, "finished"); diff --git a/js/src/jit-test/tests/debug/bug1891662.js b/js/src/jit-test/tests/debug/bug1891662.js new file mode 100644 index 0000000000..0a68f33d0c --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1891662.js @@ -0,0 +1,24 @@ +// |jit-test| --baseline-eager + +var src = ""; +for (var i = 0; i < 100; i++) { + src += "function foo" + i + "() { foo" + (i+1) + "(); }" +} +eval(src); + +function foo100() { + let g = newGlobal({newCompartment: true}); + let d = new g.Debugger(this); + + // When we set this debugger hook, we will trigger 100 + // baseline recompilations. We want an OOM to occur + // during one of those recompilations. We allocate + // about 400 times before starting recompilation, and + // about 30000 times total. This number is picked to + // leave a healthy margin on either side. + oomAtAllocation(5000); + d.onEnterFrame = () => {} +} +try { + foo0(); +} catch {} diff --git a/js/src/jit-test/tests/debug/bug1893554.js b/js/src/jit-test/tests/debug/bug1893554.js new file mode 100644 index 0000000000..dfe76c8b41 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1893554.js @@ -0,0 +1,31 @@ +let g = newGlobal({ newCompartment: true }); +g.parent = this; +g.eval( + "(" + + function () { + Debugger(parent).onExceptionUnwind = function (frame) { + frame.older; + }; + } + + ")()" +); +function f(x, y) { + try { + Object.setPrototypeOf( + y, + new Proxy(Object.getPrototypeOf(y), { + get(a, b, c) { + return undefined; + }, + }) + ); + } catch (e) {} +} +function h(x, y) { + f(x, y); +} +oomTest(function () { + h("", undefined); + h("", ""); + "".replaceAll(); +}); diff --git a/js/src/jit-test/tests/environments/1890252.js b/js/src/jit-test/tests/environments/1890252.js new file mode 100644 index 0000000000..b44ca2253f --- /dev/null +++ b/js/src/jit-test/tests/environments/1890252.js @@ -0,0 +1,15 @@ +// |jit-test| --more-compartments + +var threw = false; +try { + const v2 = evalcx("lazy"); + const o4 = { + "global": v2, + }; + o4.envChainObject = v2; + evaluate("{ let eval = parseInt; eval()}", o4); +} catch (e) { + threw = true; + assertEq(e.toString().includes("envChainObject"), true); +} +assertEq(threw, true); diff --git a/js/src/jit-test/tests/gc/bug-1651001-1.js b/js/src/jit-test/tests/gc/bug-1651001-1.js new file mode 100644 index 0000000000..889ab4a5b9 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1651001-1.js @@ -0,0 +1,9 @@ +gczeal(0); +try { + v2 = (x = []); + g1 = newGlobal({ sameZoneAs: x }); + enableShellAllocationMetadataBuilder(); + g1.Uint8ClampedArray.prototype = b1; +} catch(e) {} +startgc(9); +recomputeWrappers(); diff --git a/js/src/jit-test/tests/gc/bug-1651001-2.js b/js/src/jit-test/tests/gc/bug-1651001-2.js new file mode 100644 index 0000000000..ad093ddb22 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1651001-2.js @@ -0,0 +1,6 @@ +gczeal(0); +g1 = newGlobal({ sameZoneAs: this }); +enableShellAllocationMetadataBuilder(); +g1.Object; +startgc(1); +recomputeWrappers(); diff --git a/js/src/jit-test/tests/gc/bug-1890670.js b/js/src/jit-test/tests/gc/bug-1890670.js new file mode 100644 index 0000000000..e73c5a99e6 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1890670.js @@ -0,0 +1,12 @@ +// |jit-test| --enable-symbols-as-weakmap-keys; skip-if: getBuildConfiguration("release_or_beta") + +gczeal(0); +let wm = new WeakMap(); +let s = Symbol(); +wm.set(s, new WeakMap()); +let ss = Symbol(); +wm.get(s).set(this, ss); +let wm2 = new WeakMap(); +wm2.set(ss, "test"); +ss = null; +gc(); diff --git a/js/src/jit-test/tests/gc/bug-1892564.js b/js/src/jit-test/tests/gc/bug-1892564.js new file mode 100644 index 0000000000..c834615b91 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1892564.js @@ -0,0 +1,50 @@ +try { evalInWorker(` +gczeal(0); +try { + Object.defineProperty(this, "x", { + value:{ + parseInt: parseInt, + } + }); +} catch(exc) {} +function dummyAssertCallFunction(f) {} +try { evaluate(\` +(function(global) { + var ObjectCreate = global.Object.create; + var ObjectDefineProperty = global.Object.defineProperty; + function ArrayPush(arr, val) { + var desc = ObjectCreate(null); + desc.value = val; + desc.enumerable = true; + desc.configurable = true; + desc.writable = true; + ObjectDefineProperty(arr, arr.length, desc); + } + var testCasesArray = []; + function TestCase(d, e, a, r) { + this.description = d; + ArrayPush(testCasesArray, this); + } + global.TestCase = TestCase; +})(this); +(function f42(x99) { + new TestCase(new ArrayBuffer()); + f42(x99) + function t9() {} +})(); +\`); } catch(exc) {} +try { evaluate(\` +gczeal(14); +(function(global) { + global.makeIterator = function makeIterator(overrides) { + global.assertThrowsValue = function assertThrowsValue(f, val, msg) {}; + } + global.assertDeepEq = (function(){ + var call = Function.prototype.call, + Symbol_description = call.bind(Object.getOwnPropertyDescriptor(Symbol.prototype, "description").get), + Map_has = call.bind(Map.prototype.has), + Object_getOwnPropertyNames = Object.getOwnPropertyNames; + })(); +})(this); +\`); } catch(exc) {} +`); throw "x"; } catch(exc) {} diff --git a/js/src/jit-test/tests/gc/bug-1893984.js b/js/src/jit-test/tests/gc/bug-1893984.js new file mode 100644 index 0000000000..743be86f4e --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1893984.js @@ -0,0 +1,3 @@ +gczeal(4, 0) +a = /b/ +Object.defineProperty(a, this + this, {}) diff --git a/js/src/jit-test/tests/gc/bug-1894025.js b/js/src/jit-test/tests/gc/bug-1894025.js new file mode 100644 index 0000000000..06ebd8ceb6 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1894025.js @@ -0,0 +1,15 @@ +gczeal(0); + +var ex; +function makeExtensibleStrFrom() { + strstrstr; +} +a = makeExtensibleStrFrom; +b = newDependentString(a, 0, 50, { tenured: false }) +var exc; +try { + c = newDependentString(b, 0, { tenured: true }) +} catch (e) { + exc = e; +} +assertEq(Boolean(exc), true, "b and c required to be in different heaps but are the same"); diff --git a/js/src/jit-test/tests/gc/bug-1894442.js b/js/src/jit-test/tests/gc/bug-1894442.js new file mode 100644 index 0000000000..93bedb74de --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1894442.js @@ -0,0 +1,6 @@ +// |jit-test| --enable-symbols-as-weakmap-keys; skip-if: helperThreadCount() === 0 || getBuildConfiguration("release_or_beta") +evalInWorker(` + a = new WeakSet + a.add(Symbol.hasInstance) + gczeal(14)(0 .b) +`) diff --git a/js/src/jit-test/tests/gc/bug-1894547.js b/js/src/jit-test/tests/gc/bug-1894547.js new file mode 100644 index 0000000000..78a3cff036 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1894547.js @@ -0,0 +1,14 @@ +// |jit-test| --enable-symbols-as-weakmap-keys; skip-if: getBuildConfiguration("release_or_beta") + +gczeal(0); +let wm = new WeakMap(); +let s = {}; +wm.set(s, new WeakMap()); +let ss = {x: Symbol()}; +wm.get(s).set(this, ss); +let wm2 = new WeakMap(); +wm2.set(ss, "test"); +ss = null; + +// Collect only this zone and not the zone containing the symbol. +gc({}); diff --git a/js/src/jit-test/tests/gc/bug-1895842.js b/js/src/jit-test/tests/gc/bug-1895842.js new file mode 100644 index 0000000000..3f41d10020 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1895842.js @@ -0,0 +1,5 @@ +// |jit-test| skip-if: helperThreadCount() === 0 +evalInWorker(` + enqueueMark("drain") + startgc() +`) diff --git a/js/src/jit-test/tests/gc/marking-thread-count.js b/js/src/jit-test/tests/gc/marking-thread-count.js index 5b90e14186..3c2f7ed30d 100644 --- a/js/src/jit-test/tests/gc/marking-thread-count.js +++ b/js/src/jit-test/tests/gc/marking-thread-count.js @@ -1,12 +1,18 @@ // |jit-test| skip-if: helperThreadCount() === 0 -let initialGCHelperThreadCount = gcparam('helperThreadCount'); +// Allow maximum number of helper threads +gcparam('maxHelperThreads', 8); +gcparam('helperThreadRatio', 100); + +check(); -let prevHelperThreadCount = helperThreadCount(); for (let i of [0, 1, 4, 8, 4, 0]) { - gcparam('markingThreadCount', i); - assertEq(gcparam('markingThreadCount'), i); - assertEq(gcparam('helperThreadCount'), initialGCHelperThreadCount); - assertEq(true, helperThreadCount() >= Math.max(prevHelperThreadCount, i)); - prevHelperThreadCount = helperThreadCount(); + gcparam('maxMarkingThreads', i); + assertEq(gcparam('maxMarkingThreads'), i); + check(); +} + +function check() { + assertEq(gcparam('markingThreadCount') <= gcparam('maxMarkingThreads'), true); + assertEq(gcparam('markingThreadCount') < gcparam('helperThreadCount'), true); } diff --git a/js/src/jit-test/tests/ion/bug1608256.js b/js/src/jit-test/tests/ion/bug1608256.js index 4445a41157..0929f9d3f6 100644 --- a/js/src/jit-test/tests/ion/bug1608256.js +++ b/js/src/jit-test/tests/ion/bug1608256.js @@ -1,4 +1,4 @@ -// |jit-test| --no-threads; --baseline-warmup-threshold=1; --ion-full-warmup-threshold=1 +// |jit-test| --no-threads; --baseline-warmup-threshold=1 function g(obj, v) { obj.prop = v; } diff --git a/js/src/jit-test/tests/ion/bug1894456-1.js b/js/src/jit-test/tests/ion/bug1894456-1.js new file mode 100644 index 0000000000..2b8f3e0afe --- /dev/null +++ b/js/src/jit-test/tests/ion/bug1894456-1.js @@ -0,0 +1,6 @@ +var f32 = new Float32Array(1); +for (var i = 0; i < 50; i++) { + if (f32[0] != null) { + } + try {} catch {} +} diff --git a/js/src/jit-test/tests/ion/bug1894456-2.js b/js/src/jit-test/tests/ion/bug1894456-2.js new file mode 100644 index 0000000000..20b019738d --- /dev/null +++ b/js/src/jit-test/tests/ion/bug1894456-2.js @@ -0,0 +1,6 @@ +var f32 = new Float32Array(1); +for (var i = 0; i < 50; i++) { + if (f32[0] != undefined) { + } + try {} catch {} +} diff --git a/js/src/jit-test/tests/ion/depended-on-bit-1.js b/js/src/jit-test/tests/ion/depended-on-bit-1.js new file mode 100644 index 0000000000..418e7accaa --- /dev/null +++ b/js/src/jit-test/tests/ion/depended-on-bit-1.js @@ -0,0 +1,52 @@ +var dependedOnStrings = []; + +var length = 50; + +function getSubstr(src, i) { + return src.substring(i, i + 50); +} + +function checkProp(o, prop) { + return o[prop]; +} + +var substrs = []; +var objs = []; + +with({}) +for (var i = 0; i < 1000; i++) { + var pieces = []; + for (var j = 0; j < 99; j++) { + pieces.push("a"); + pieces.push(Math.floor(Math.random() * 10)); + } + dependedOnStrings.push(pieces.join("")); +} + +for (var i = 0; i < 1000; i++) { + // Create a bunch of substrings depending on strings in dependedOnStrings + substrs.push(getSubstr(dependedOnStrings[i], (i * 2) % 50)); + objs.push({}); +} + +for (var i = 0; i < 1000; i++) { + // Use the depended on strings as keys to get them replaced with + // JSAtomRefStrings + checkProp(objs[i], dependedOnStrings[i]); +} + +// Use a bunch of memory to try to ensure that we overwrite the buffers +// that could have erroneously been freed +for (var i = 0; i < 1000; i++) { + var pieces = []; + for (var j = 0; j < 99; j++) { + pieces.push("b"); + pieces.push(Math.floor(Math.random() * 10)); + } + dependedOnStrings.push(pieces.join("")); +} + +// Ensure the buffers were not in fact freed +for (var i = 0; i < 1000; i++) { + assertEq(substrs[i].startsWith("a"), true); +} diff --git a/js/src/jit-test/tests/ion/depended-on-bit-2.js b/js/src/jit-test/tests/ion/depended-on-bit-2.js new file mode 100644 index 0000000000..d4090df949 --- /dev/null +++ b/js/src/jit-test/tests/ion/depended-on-bit-2.js @@ -0,0 +1,56 @@ +var dependedOnStrings = []; + +var length = 50; +var reg = /q[a0-9]{50}/; + +function getSubstr(src, i) { + return reg.exec(src)[0]; +} + +function checkProp(o, prop) { + return o[prop]; +} + +var substrs = []; +var objs = []; + +with({}) +for (var i = 0; i < 1000; i++) { + var pieces = []; + for (var j = 0; j < 99; j++) { + if (j == (i * 2) % 50) { + pieces.push("q"); + } + pieces.push("a"); + pieces.push(Math.floor(Math.random() * 10)); + } + dependedOnStrings.push(pieces.join("")); +} + +for (var i = 0; i < 1000; i++) { + // Create a bunch of substrings depending on strings in dependedOnStrings + substrs.push(getSubstr(dependedOnStrings[i], (i * 2) % 50)); + objs.push({}); +} + +for (var i = 0; i < 1000; i++) { + // Use the depended on strings as keys to get them replaced with + // JSAtomRefStrings + checkProp(objs[i], dependedOnStrings[i]); +} + +// Use a bunch of memory to try to ensure that we overwrite the buffers +// that could have erroneously been freed +for (var i = 0; i < 1000; i++) { + var pieces = []; + for (var j = 0; j < 99; j++) { + pieces.push("b"); + pieces.push(Math.floor(Math.random() * 10)); + } + dependedOnStrings.push(pieces.join("")); +} + +// Ensure the buffers were not in fact freed +for (var i = 0; i < 1000; i++) { + assertEq(substrs[i].startsWith("qa"), true); +} diff --git a/js/src/jit-test/tests/modules/dynamic-import-unsupported-attribute.js b/js/src/jit-test/tests/modules/dynamic-import-unsupported-attribute.js new file mode 100644 index 0000000000..2b6ccb80bf --- /dev/null +++ b/js/src/jit-test/tests/modules/dynamic-import-unsupported-attribute.js @@ -0,0 +1,29 @@ +// |jit-test| --enable-import-attributes + +async function test() { + try { + await import('./not-a-real-file.json', {with:{'unsupportedAttributeKey': 'json'}}); + throw new Error("unreachable"); + } catch (error) { + assertEq(error instanceof TypeError, true); + assertEq(error.message, "Unsupported import attribute: unsupportedAttributeKey"); + } + + try { + await import('./not-a-real-file.json', {with:{'unsupportedAttributeKey': 'json', type: 'json'}}); + throw new Error("unreachable"); + } catch (error) { + assertEq(error instanceof TypeError, true); + assertEq(error.message, "Unsupported import attribute: unsupportedAttributeKey"); + } + + try { + await import('./not-a-real-file.json', {with:{type: 'json', 'unsupportedAttributeKey': 'json'}}); + throw new Error("unreachable"); + } catch (error) { + assertEq(error instanceof TypeError, true); + assertEq(error.message, "Unsupported import attribute: unsupportedAttributeKey"); + } +} + +test();
\ No newline at end of file diff --git a/js/src/jit-test/tests/modules/failure-on-resume.js b/js/src/jit-test/tests/modules/failure-on-resume.js new file mode 100644 index 0000000000..d0e716b0bb --- /dev/null +++ b/js/src/jit-test/tests/modules/failure-on-resume.js @@ -0,0 +1,79 @@ +const dbgGlobal = newGlobal({ newCompartment: true }); +dbgGlobal.parent = this; +dbgGlobal.eval(` +var entered = 0; +var forceReturn = false; +Debugger(parent).onEnterFrame = function () { + entered++; + if (forceReturn) { + return { return: "force return" }; + } + return undefined; +}; +`); + +{ + function* f1() { yield 10; }; + + dbgGlobal.entered = 0; + let g = f1(); + assertEq(dbgGlobal.entered, 1); + dbgGlobal.forceReturn = true; + let ret = g.next(); + dbgGlobal.forceReturn = false; + assertEq(dbgGlobal.entered, 2); + + assertEq(ret.value, "force return"); +} + +{ + async function f2() { await {}; } + + dbgGlobal.entered = 0; + let p = f2(); + assertEq(dbgGlobal.entered, 1); + dbgGlobal.forceReturn = true; + drainJobQueue(); + dbgGlobal.forceReturn = false; + assertEq(dbgGlobal.entered, 2); + + let ret = null; + p.then(x => ret = x); + drainJobQueue(); + assertEq(ret, "force return"); +} + +{ + async function* f3() { await {}; } + + dbgGlobal.entered = 0; + let g = f3(); + assertEq(dbgGlobal.entered, 1); + dbgGlobal.forceReturn = true; + let p = g.next(); + dbgGlobal.forceReturn = false; + assertEq(dbgGlobal.entered, 2); + + let ret = null; + p.then(v => ret = v); + drainJobQueue(); + assertEq(ret.value, "force return"); +} + +{ + let m = registerModule("1", parseModule("await {};")); + moduleLink(m); + + dbgGlobal.entered = 0; + let p = moduleEvaluate(m); + assertEq(dbgGlobal.entered, 1); + dbgGlobal.forceReturn = true; + drainJobQueue(); + dbgGlobal.forceReturn = false; + assertEq(dbgGlobal.entered, 2); + + let ret = null; + p.then(x => ret = x); + drainJobQueue(); + assertEq(ret, undefined); +} diff --git a/js/src/jit-test/tests/modules/import-entries.js b/js/src/jit-test/tests/modules/import-entries.js index e20d4e119b..ed668f0069 100644 --- a/js/src/jit-test/tests/modules/import-entries.js +++ b/js/src/jit-test/tests/modules/import-entries.js @@ -1,21 +1,24 @@ -// |jit-test| --enable-import-assertions +// |jit-test| --enable-import-attributes // Test importEntries property -function assertionEq(actual, expected) { - var actualAssertions = actual['assertions']; - var expectedAssertions = expected['assertions']; +function attributeEq(actual, expected) { + var actualAttributes = actual['attributes']; + var expectedAttributes = expected['attributes']; - if(actualAssertions === null) { - return expectedAssertions === actualAssertions + if(actualAttributes === null) { + return expectedAttributes === actualAttributes } - if(actualAssertions.length !== expectedAssertions.length) { + if(actualAttributes.length !== expectedAttributes.length) { return false; } - for (var i = 0; i < expected.length; i++) { - if(expected[i].type !== actual[i].type) { + for (var i = 0; i < expectedAttributes.length; i++) { + if ( + expectedAttributes[i]['key'] !== actualAttributes[i]['key'] || + expectedAttributes[i]['value'] !== actualAttributes[i]['value'] + ) { return false; } } @@ -28,7 +31,7 @@ function importEntryEq(a, b) { a['importName'] === b['importName'] && a['localName'] === b['localName']; - return r1 && assertionEq(a['moduleRequest'], b['moduleRequest']); + return r1 && attributeEq(a['moduleRequest'], b['moduleRequest']); } function findImportEntry(array, target) @@ -54,34 +57,34 @@ function testImportEntries(source, expected) { testImportEntries('', []); testImportEntries('import v from "mod";', - [{moduleRequest: {specifier: 'mod', assertions: null}, importName: 'default', localName: 'v'}]); + [{moduleRequest: {specifier: 'mod', attributes: null}, importName: 'default', localName: 'v'}]); testImportEntries('import * as ns from "mod";', - [{moduleRequest: {specifier: 'mod', assertions: null}, importName: null, localName: 'ns'}]); + [{moduleRequest: {specifier: 'mod', attributes: null}, importName: null, localName: 'ns'}]); testImportEntries('import {x} from "mod";', - [{moduleRequest: {specifier: 'mod', assertions: null}, importName: 'x', localName: 'x'}]); + [{moduleRequest: {specifier: 'mod', attributes: null}, importName: 'x', localName: 'x'}]); testImportEntries('import {x as v} from "mod";', - [{moduleRequest: {specifier: 'mod', assertions: null}, importName: 'x', localName: 'v'}]); + [{moduleRequest: {specifier: 'mod', attributes: null}, importName: 'x', localName: 'v'}]); testImportEntries('import "mod";', []); testImportEntries('import {x} from "a"; import {y} from "b";', - [{moduleRequest: {specifier: 'a', assertions: null}, importName: 'x', localName: 'x'}, - {moduleRequest: {specifier: 'b', assertions: null}, importName: 'y', localName: 'y'}]); + [{moduleRequest: {specifier: 'a', attributes: null}, importName: 'x', localName: 'x'}, + {moduleRequest: {specifier: 'b', attributes: null}, importName: 'y', localName: 'y'}]); if (getRealmConfiguration("importAttributes")) { - testImportEntries('import v from "mod" assert {};', - [{moduleRequest: {specifier: 'mod', assertions: null}, importName: 'default', localName: 'v'}]); + testImportEntries('import v from "mod" with {};', + [{moduleRequest: {specifier: 'mod', attributes: null}, importName: 'default', localName: 'v'}]); - testImportEntries('import v from "mod" assert { type: "js"};', - [{moduleRequest: {specifier: 'mod', assertions: [{ type: 'js'}]}, importName: 'default', localName: 'v'}]); + testImportEntries('import v from "mod" with { type: "js"};', + [{moduleRequest: {specifier: 'mod', attributes: [{ key: 'type', value: 'js'}]}, importName: 'default', localName: 'v'}]); - testImportEntries('import {x} from "mod" assert { type: "js"};', - [{moduleRequest: {specifier: 'mod', assertions: [{ type: 'js'}]}, importName: 'x', localName: 'x'}]); + testImportEntries('import {x} from "mod" with { type: "js"};', + [{moduleRequest: {specifier: 'mod', attributes: [{ key: 'type', value: 'js'}]}, importName: 'x', localName: 'x'}]); - testImportEntries('import {x as v} from "mod" assert { type: "js"};', - [{moduleRequest: {specifier: 'mod', assertions: [{ type: 'js'}]}, importName: 'x', localName: 'v'}]); + testImportEntries('import {x as v} from "mod" with { type: "js"};', + [{moduleRequest: {specifier: 'mod', attributes: [{ key: 'type', value: 'js'}]}, importName: 'x', localName: 'v'}]); } diff --git a/js/src/jit-test/tests/modules/import-unsupported-attribute.js b/js/src/jit-test/tests/modules/import-unsupported-attribute.js new file mode 100644 index 0000000000..c771c6556b --- /dev/null +++ b/js/src/jit-test/tests/modules/import-unsupported-attribute.js @@ -0,0 +1,2 @@ +// |jit-test| --enable-import-attributes; module; error: TypeError: Unsupported import attribute: unsupported +import a from 'foo' with { unsupported: 'test'} diff --git a/js/src/jit-test/tests/modules/requested-modules.js b/js/src/jit-test/tests/modules/requested-modules.js index ebbf8ce6c1..e0a7fb66b8 100644 --- a/js/src/jit-test/tests/modules/requested-modules.js +++ b/js/src/jit-test/tests/modules/requested-modules.js @@ -1,4 +1,4 @@ -// |jit-test| --enable-import-assertions +// |jit-test| --enable-import-attributes // Test requestedModules property @@ -8,15 +8,16 @@ function testRequestedModules(source, expected) { assertEq(actual.length, expected.length); for (var i = 0; i < actual.length; i++) { assertEq(actual[i].moduleRequest.specifier, expected[i].specifier); - if(expected[i].assertions === null) { - assertEq(actual[i].moduleRequest.assertions, null); + if(expected[i].attributes === null) { + assertEq(actual[i].moduleRequest.attributes, null); } else { - var expectedAssertions = expected[i].assertions; - var actualAssertions = actual[i].moduleRequest.assertions; - assertEq(actualAssertions.length, expectedAssertions.length); - for (var j = 0; j < expectedAssertions.length; j++) { - assertEq(expectedAssertions[j].type, actualAssertions[j].type); + var expectedAttributes = expected[i].attributes; + var actualAttributes = actual[i].moduleRequest.attributes; + assertEq(actualAttributes.length, expectedAttributes.length); + for (var j = 0; j < expectedAttributes.length; j++) { + assertEq(expectedAttributes[j]['key'], actualAttributes[j]['key']); + assertEq(expectedAttributes[j]['value'], actualAttributes[j]['value']); } } } @@ -25,71 +26,71 @@ function testRequestedModules(source, expected) { testRequestedModules("", []); testRequestedModules("import a from 'foo'", [ - { specifier: 'foo', assertions: null } + { specifier: 'foo', attributes: null } ]); testRequestedModules("import a from 'foo'; import b from 'bar'", [ - { specifier: 'foo', assertions: null }, - { specifier: 'bar', assertions: null } + { specifier: 'foo', attributes: null }, + { specifier: 'bar', attributes: null } ]); testRequestedModules("import a from 'foo'; import b from 'bar'; import c from 'foo'", [ - { specifier: 'foo', assertions: null }, - { specifier: 'bar', assertions: null } + { specifier: 'foo', attributes: null }, + { specifier: 'bar', attributes: null } ]); testRequestedModules("export {} from 'foo'", [ - { specifier: 'foo', assertions: null } + { specifier: 'foo', attributes: null } ]); testRequestedModules("export * from 'bar'",[ - { specifier: 'bar', assertions: null } + { specifier: 'bar', attributes: null } ]); testRequestedModules("import a from 'foo'; export {} from 'bar'; export * from 'baz'", [ - { specifier: 'foo', assertions: null }, - { specifier: 'bar', assertions: null }, - { specifier: 'baz', assertions: null } + { specifier: 'foo', attributes: null }, + { specifier: 'bar', attributes: null }, + { specifier: 'baz', attributes: null } ]); if (getRealmConfiguration("importAttributes")) { - testRequestedModules("import a from 'foo' assert {}", [ - { specifier: 'foo', assertions: null }, + testRequestedModules("import a from 'foo' with {}", [ + { specifier: 'foo', attributes: null }, ]); - testRequestedModules("import a from 'foo' assert { type: 'js'}", [ - { specifier: 'foo', assertions: [ { type: 'js' } ] }, + testRequestedModules("import a from 'foo' with { type: 'js'}", [ + { specifier: 'foo', attributes: [ { key: 'type', value: 'js'} ] }, ]); - testRequestedModules("import a from 'foo' assert { unsupported: 'test'}", [ - { specifier: 'foo', assertions: null }, + testRequestedModules("import a from 'foo' with { unsupported: 'test'}", [ + { specifier: 'foo', attributes: [ { key: 'unsupported', value: 'test'} ] }, ]); - testRequestedModules("import a from 'foo' assert { unsupported: 'test', type: 'js', foo: 'bar' }", [ - { specifier: 'foo', assertions: [ { type: 'js' } ] }, + testRequestedModules("import a from 'foo' with { unsupported: 'test', type: 'js', foo: 'bar' }", [ + { specifier: 'foo', attributes: [ { key: 'unsupported', value: 'test'}, { key: 'type', value: 'js'}, { key: 'foo', value: 'bar'} ] }, ]); - testRequestedModules("import a from 'foo' assert { type: 'js1'}; export {} from 'bar' assert { type: 'js2'}; export * from 'baz' assert { type: 'js3'}", [ - { specifier: 'foo', assertions: [ { type: 'js1' } ] }, - { specifier: 'bar', assertions: [ { type: 'js2' } ] }, - { specifier: 'baz', assertions: [ { type: 'js3' } ] } + testRequestedModules("import a from 'foo' with { type: 'js1'}; export {} from 'bar' with { type: 'js2'}; export * from 'baz' with { type: 'js3'}", [ + { specifier: 'foo', attributes: [ { key: 'type', value: 'js1'} ] }, + { specifier: 'bar', attributes: [ { key: 'type', value: 'js2'} ] }, + { specifier: 'baz', attributes: [ { key: 'type', value: 'js3'} ] } ]); - testRequestedModules("export {} from 'foo' assert { type: 'js'}", [ - { specifier: 'foo', assertions: [ { type: 'js' } ] } + testRequestedModules("export {} from 'foo' with { type: 'js'}", [ + { specifier: 'foo', attributes: [ { key: 'type', value: 'js'} ] } ]); - testRequestedModules("export * from 'bar' assert { type: 'json'}",[ - { specifier: 'bar', assertions: [ { type: 'json' } ] } + testRequestedModules("export * from 'bar' with { type: 'json'}",[ + { specifier: 'bar', attributes: [ { key: 'type', value: 'json'} ] } ]); - testRequestedModules("import a from 'foo'; import b from 'bar' assert { type: 'json' };", [ - { specifier: 'foo', assertions: null }, - { specifier: 'bar', assertions: [ { type: 'json' } ] }, + testRequestedModules("import a from 'foo'; import b from 'bar' with { type: 'json' };", [ + { specifier: 'foo', attributes: null }, + { specifier: 'bar', attributes: [ { key: 'type', value: 'json'} ] }, ]); - testRequestedModules("import b from 'bar' assert { type: 'json' }; import a from 'foo';", [ - { specifier: 'bar', assertions: [ { type: 'json' } ] }, - { specifier: 'foo', assertions: null }, + testRequestedModules("import b from 'bar' with { type: 'json' }; import a from 'foo';", [ + { specifier: 'bar', attributes: [ { key: 'type', value: 'json'} ] }, + { specifier: 'foo', attributes: null }, ]); } diff --git a/js/src/jit-test/tests/sharedbuf/size-with-uninitialized.js b/js/src/jit-test/tests/sharedbuf/size-with-uninitialized.js new file mode 100644 index 0000000000..14f6e038ce --- /dev/null +++ b/js/src/jit-test/tests/sharedbuf/size-with-uninitialized.js @@ -0,0 +1,15 @@ +// OOM during SharedArrayBuffer initialization can expose partially initialized +// object to metadata builder. +// It shouldn't crash. + +newGlobal({ newCompartment: true }).Debugger(this).memory.trackingAllocationSites = true; +for (let i = 0; i < 9; i++) { + oomTest(function () { + class C extends WebAssembly.Memory {} + new C({ + initial: 0, + maximum: 1, + shared: 1, + }); + }); +} diff --git a/js/src/jit-test/tests/typedarray/arraybuffer-zero-length-alignment-check.js b/js/src/jit-test/tests/typedarray/arraybuffer-zero-length-alignment-check.js new file mode 100644 index 0000000000..d706977a6e --- /dev/null +++ b/js/src/jit-test/tests/typedarray/arraybuffer-zero-length-alignment-check.js @@ -0,0 +1,29 @@ +// Iterate a few times to increase the likelihood for malloc(0) to return a +// non-aligned memory. +for (let i = 0; i < 100; ++i) { + // Create a zero-length buffer with malloc'ed memory. + let ab = createExternalArrayBuffer(0); + + // Create a typed array which requires 8-byte alignment. + let source = new Float64Array(ab); + + // This call shouldn't assert when copying zero bytes from |source|, even when + // the memory is non-aligned. + let target = new Float64Array(source); + + // Add some uses of the objects. + assertEq(ab.byteLength, 0); + assertEq(source.byteLength, 0); + assertEq(target.byteLength, 0); +} + +// Repeat the above tests with the |createUserArrayBuffer| testing function. +for (let i = 0; i < 100; ++i) { + let ab = createUserArrayBuffer(0); + let source = new Float64Array(ab); + let target = new Float64Array(source); + + assertEq(ab.byteLength, 0); + assertEq(source.byteLength, 0); + assertEq(target.byteLength, 0); +} diff --git a/js/src/jit-test/tests/typedarray/sort-trampoline.js b/js/src/jit-test/tests/typedarray/sort-trampoline.js new file mode 100644 index 0000000000..9af8080b52 --- /dev/null +++ b/js/src/jit-test/tests/typedarray/sort-trampoline.js @@ -0,0 +1,174 @@ +function testGC() { + for (var i = 0; i < 20; i++) { + var arr = new BigInt64Array([1n, 3n, 0n, 12345678901234n, -12345678901234567n]); + arr.sort((x, y) => { + if (i === 17) { + gc(); + } + return Number(x) - Number(y); + }); + assertEq(arr.join(), "-12345678901234567,0,1,3,12345678901234"); + } +} +testGC(); + +function testDetach() { + for (var i = 0; i < 20; i++) { + var arr = new Float32Array([1, 2.5, -0, 3]); + arr.sort((x, y) => { + if (i > 15) { + detachArrayBuffer(arr.buffer); + } + return x - y; + }); + if (i > 15) { + assertEq(arr.length, 0); + assertEq(arr[0], undefined); + } else { + assertEq(arr.join(","), "0,1,2.5,3"); + } + } +} +testDetach(); + +function testException() { + var arr = new BigInt64Array([1n, 3n, 0n, 4n, 1n]); + var ex; + try { + for (var i = 0; i < 20; i++) { + arr.sort((x, y) => { + if (i === 17) { + throw "fit"; + } + return Number(x) - Number(y); + }); + } + } catch (e) { + ex = e; + } + assertEq(ex, "fit"); + assertEq(i, 17); + assertEq(arr.join(), "0,1,1,3,4"); + +} +testException(); + +function testRectifier() { + var arr = new Uint32Array([0xffff, 0xffff_ffff, -1, 0]); + for (var i = 0; i < 20; i++) { + arr.sort(function(x, y, a) { + assertEq(arguments.length, 2); + assertEq(a, undefined); + return y - x; + }); + } + assertEq(arr.join(), "4294967295,4294967295,65535,0"); +} +testRectifier(); + +function testClassConstructor() { + var normal = (x, y) => x - y; + var dummy = {}; + var ctor = (class { constructor(x, y) { + assertEq(x, dummy); + }}); + // Warm up the constructor. + for (var i = 0; i < 20; i++) { + new ctor(dummy, dummy); + } + for (var i = 0; i < 20; i++) { + var arr = new Uint8Array([0, 5, 1, 3]); + var ex; + try { + arr.sort(i < 17 ? normal : ctor); + } catch (e) { + ex = e; + } + assertEq(ex instanceof TypeError, i >= 17); + assertEq(arr.join(""), i >= 17 ? "0513" : "0135"); + } +} +testClassConstructor(); + +function testSwitchRealms() { + var arr = new Uint8ClampedArray([5, 3, 0, 1]); + var g = newGlobal({sameCompartmentAs: this}); + g.foo = 123; + var comp = g.evaluate(`((x, y) => { + assertEq(foo, 123); + return x - y; + })`); + for (var i = 0; i < 20; i++) { + arr.sort(comp); + } + assertEq(arr.join(""), "0135"); +} +testSwitchRealms(); + +function testCrossCompartment() { + var g = newGlobal({newCompartment: true}); + var wrapper = g.evaluate(`((x, y) => { + return Number(x) - Number(y); + })`); + for (var i = 0; i < 20; i++) { + var arr = new BigUint64Array([1n, 0n, 1234567890123n, 98765432109876n, 0n]); + arr.sort(wrapper); + assertEq(arr.join(), "0,0,1,1234567890123,98765432109876"); + } +} +testCrossCompartment(); + +function testBound() { + var fun = (function(a, x, y) { + "use strict"; + assertEq(this, null); + assertEq(a, 1); + return Number(x) - Number(y); + }).bind(null, 1); + for (var i = 0; i < 20; i++) { + var arr = new BigUint64Array([1n, 0n, 1234567890123n, 98765432109876n, 0n]); + arr.sort(fun); + assertEq(arr.join(), "0,0,1,1234567890123,98765432109876"); + } +} +testBound(); + +function testExtraArgs() { + var arr = new Int8Array([1, 3, 5, 0]); + var cmp = (x, y) => x - y - 1; + for (var i = 0; i < 20; i++) { + arr.sort(cmp, cmp, cmp, cmp, cmp, cmp, cmp); + } + assertEq(arr.join(""), "1035"); +} +testExtraArgs(); + +function testBailout() { + for (var i = 0; i < 110; i++) { + var arr = new Float64Array([1, 3.3, 5.5, -0, NaN]); + arr.sort(function(x, y) { + if (i === 108) { + bailout(); + } + return x - y; + }); + assertEq(arr.join(","), "0,1,3.3,5.5,NaN"); + } +} +testBailout(); + +function testExceptionHandlerSwitchRealm() { + var g = newGlobal({sameCompartmentAs: this}); + for (var i = 0; i < 25; i++) { + var ex = null; + try { + Int8Array.prototype.toSorted.call(new Int8Array([1, 2, 3]), () => { + throw "fit"; + }); + } catch (e) { + ex = e; + } + assertEq(ex, "fit"); + } +} +testExceptionHandlerSwitchRealm(); diff --git a/js/src/jit-test/tests/typedarray/sort-wrapper.js b/js/src/jit-test/tests/typedarray/sort-wrapper.js new file mode 100644 index 0000000000..f646ccd0a5 --- /dev/null +++ b/js/src/jit-test/tests/typedarray/sort-wrapper.js @@ -0,0 +1,21 @@ +function test() { + var g = newGlobal({ newCompartment: true }); + var wrapped = g.evaluate("new Int32Array([1, 3, 2, 0])"); + var wrappedOther = g.evaluate("({})"); + var unwrapped = new Int32Array(10); + + var ex; + try { + unwrapped.sort.call(wrappedOther); + } catch (e) { + ex = e; + } + assertEq(ex instanceof TypeError, true); + + assertEq(unwrapped.sort.call(wrapped), wrapped); + assertEq(wrapped.toString(), "0,1,2,3"); + + assertEq(unwrapped.sort.call(wrapped, (a, b) => b - a), wrapped); + assertEq(wrapped.toString(), "3,2,1,0"); +} +test(); diff --git a/js/src/jit-test/tests/warp/bug1683306.js b/js/src/jit-test/tests/warp/bug1683306.js index 3c74c3f612..193da36af4 100644 --- a/js/src/jit-test/tests/warp/bug1683306.js +++ b/js/src/jit-test/tests/warp/bug1683306.js @@ -1,4 +1,4 @@ -// |jit-test| --ion-offthread-compile=off; --ion-full-warmup-threshold=0; --ion-gvn=off; --baseline-eager +// |jit-test| --ion-offthread-compile=off; --ion-gvn=off; --baseline-eager // // Bug 1683306 - Assertion failure: !genObj->hasStackStorage() || genObj->isStackStorageEmpty(), at vm/GeneratorObject.cpp:144 diff --git a/js/src/jit-test/tests/warp/bug1683614.js b/js/src/jit-test/tests/warp/bug1683614.js index 1d0a4724ff..58c67c7a91 100644 --- a/js/src/jit-test/tests/warp/bug1683614.js +++ b/js/src/jit-test/tests/warp/bug1683614.js @@ -1,4 +1,4 @@ -// |jit-test| --ion-offthread-compile=off; --ion-full-warmup-threshold=0; --baseline-eager; skip-if: !this.hasOwnProperty("ReadableStream") +// |jit-test| --ion-offthread-compile=off; --baseline-eager; skip-if: !this.hasOwnProperty("ReadableStream") gczeal(9, 8); function s() { } @@ -10,4 +10,4 @@ new ReadableStream({ async function test() { for (let i17 = 1; i17 <= 30; i17++) await s(0 + function () { return i17 }); -}
\ No newline at end of file +} diff --git a/js/src/jit-test/tests/wasm/branch-hinting/complex_control_flow.js b/js/src/jit-test/tests/wasm/branch-hinting/complex_control_flow.js new file mode 100644 index 0000000000..727a3156fb --- /dev/null +++ b/js/src/jit-test/tests/wasm/branch-hinting/complex_control_flow.js @@ -0,0 +1,38 @@ +// Test branch hinting with nested if. + +var imports = { "":{inc() { counter++ }} }; +counter = 0; + +let module = new WebAssembly.Module(wasmTextToBinary(`(module + (import "" "inc" (func (result i32))) + (func + (result i32) + (@metadata.code.branch_hint "\\00") (if (result i32) + (i32.const 1) + (then + (@metadata.code.branch_hint "\\00") (if (result i32) + (i32.const 2) + (then + (@metadata.code.branch_hint "\\00") (if (result i32) + (i32.const 3) + (then + (@metadata.code.branch_hint "\\00") (if (result i32) + (i32.const 0) + (then (call 0)) + (else (i32.const 42)) + ) + ) + (else (call 0)) + ) + ) + (else (call 0)) + ) + ) + (else (call 0)) + ) + ) + (export "run" (func 1)) +)`, 42, imports)); + +assertEq(counter, 0); +assertEq(wasmParsedBranchHints(module), true); diff --git a/js/src/jit-test/tests/wasm/branch-hinting/directives.txt b/js/src/jit-test/tests/wasm/branch-hinting/directives.txt new file mode 100644 index 0000000000..8fe3d1e8e4 --- /dev/null +++ b/js/src/jit-test/tests/wasm/branch-hinting/directives.txt @@ -0,0 +1 @@ +|jit-test| --setpref=wasm_branch_hinting=true; --wasm-compiler=ion; test-also=--wasm-compiler=baseline;skip-if: !wasmBranchHintingEnabled(); include:wasm.js diff --git a/js/src/jit-test/tests/wasm/branch-hinting/parsing.js b/js/src/jit-test/tests/wasm/branch-hinting/parsing.js new file mode 100644 index 0000000000..0ff9b0f557 --- /dev/null +++ b/js/src/jit-test/tests/wasm/branch-hinting/parsing.js @@ -0,0 +1,154 @@ +// Make sure we are correctly parsing this custom section. +var code =` +(module + (func $$dummy) + (func $main (param i32) (result i32) + i32.const 0 + local.get 0 + i32.eq + ;; Only allowed on br_if and if + (@metadata.code.branch_hint "\\00") if + call $$dummy + i32.const 1 + return + else + call $$dummy + i32.const 0 + return + end + i32.const 3 + return + ) + + (export "_main" (func $main)) +)`; + +let branchHintsModule = new WebAssembly.Module(wasmTextToBinary(code)); +assertEq(WebAssembly.Module.customSections(branchHintsModule, "metadata.code.branch_hint").length, 1); +assertEq(wasmParsedBranchHints(branchHintsModule), true); + +let instance = new WebAssembly.Instance(branchHintsModule); +assertEq(instance.exports._main(0), 1); + +// Testing branch hints parsing on `if` and `br_if` +branchHintsModule = new WebAssembly.Module(wasmTextToBinary(` +(module + (func $main + i32.const 0 + (@metadata.code.branch_hint "\\00") + if + i32.const 0 + (@metadata.code.branch_hint "\\01") + br_if 0 + end + ) + (export "_main" (func $main)) +)`)); +assertEq(wasmParsedBranchHints(branchHintsModule), true); +instance = new WebAssembly.Instance(branchHintsModule); +instance.exports._main(); + +let m = new WebAssembly.Module(wasmTextToBinary(` +(module + (type (;0;) (func)) + (type (;1;) (func (param i32) (result i32))) + (type (;2;) (func (result i32))) + (func $__wasm_nullptr (type 0) + unreachable) + (func $main (type 2) (result i32) + (local i32 i32 i32 i32) + i32.const 0 + local.tee 2 + local.set 3 + loop + local.get 2 + i32.const 50000 + i32.eq + (@metadata.code.branch_hint "\\00") if + i32.const 1 + local.set 3 + end + local.get 2 + i32.const 1 + i32.add + local.tee 2 + i32.const 100000 + i32.ne + (@metadata.code.branch_hint "\\01") br_if 0 (;@1;) + end + local.get 3) + (table (;0;) 1 1 funcref) + (memory (;0;) 17 128) + (global (;0;) (mut i32) (i32.const 42)) + (export "memory" (memory 0)) + (export "_main" (func $main)) + (elem (;0;) (i32.const 0) func $__wasm_nullptr) + (type (;0;) (func (param i32))) +)`)); + +assertEq(wasmParsedBranchHints(m), true); +instance = new WebAssembly.Instance(m); +assertEq(instance.exports._main(0), 1); + +// Testing invalid values for branch hints +assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(` +(module + (func $main (param i32) (result i32) + i32.const 0 + (@metadata.code.branch_hint "\\0000000") if + i32.const 1 + return + end + i32.const 42 + return + ) + ) +`)), SyntaxError, /invalid value for branch hint/); + +assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(` +(module + (func $main (param i32) (result i32) + i32.const 0 + (@metadata.code.branch_hint "\\02") if + i32.const 1 + return + end + i32.const 42 + return + ) + ) +`)), SyntaxError, /invalid value for branch hint/); + +assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(` +(module + (func $main (param i32) (result i32) + i32.const 0 + (@metadata.code.branch_hint "\\aaaa") if + i32.const 1 + return + end + i32.const 42 + return + ) + ) +`)), SyntaxError, /wasm text error/); + +assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(` +(module + (func $main (param i32) (result i32) + i32.const 0 + (@metadata.code.branch_hint) if + i32.const 1 + return + end + i32.const 42 + return + ) + ) +`)), SyntaxError, /wasm text error/); + +assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(` +(module + (@metadata.code.branch_hint) +) +`)), SyntaxError, /wasm text error/); diff --git a/js/src/jit-test/tests/wasm/branch-hinting/simple_example.js b/js/src/jit-test/tests/wasm/branch-hinting/simple_example.js new file mode 100644 index 0000000000..141ee02fef --- /dev/null +++ b/js/src/jit-test/tests/wasm/branch-hinting/simple_example.js @@ -0,0 +1,41 @@ +// Branch Hinting proposal + +function runModule(hint) { + let code =` + (module + (func $$dummy) + (func $main (param i32) (result i32) + i32.const 0 + local.get 0 + i32.eq + ;; Only allowed on br_if and if + (@metadata.code.branch_hint "${hint}") if + call $$dummy + i32.const 1 + return + else + call $$dummy + i32.const 0 + return + end + i32.const 3 + return + ) + (export "_main" (func $main)) + )`; + let branchHintsModule = new WebAssembly.Module(wasmTextToBinary(code)); + assertEq(wasmParsedBranchHints(branchHintsModule), true); + + let instance = new WebAssembly.Instance(branchHintsModule); + assertEq(instance.exports._main(0), 1); +} + +// Ensure that we have the same result with different branch hints. +runModule("\\00"); +runModule("\\01"); + +let module = new WebAssembly.Module(wasmTextToBinary(` + (func i32.const 0 (@metadata.code.branch_hint "\\00") if end) +`)) + +assertEq(wasmParsedBranchHints(module), true); diff --git a/js/src/jit-test/tests/wasm/builtin-modules/js-string/basic.js b/js/src/jit-test/tests/wasm/builtin-modules/js-string/basic.js index 2c0ecb89c5..4c789dda19 100644 --- a/js/src/jit-test/tests/wasm/builtin-modules/js-string/basic.js +++ b/js/src/jit-test/tests/wasm/builtin-modules/js-string/basic.js @@ -34,14 +34,14 @@ let testModule = `(module (func (import "wasm:js-string" "fromCharCode") (param i32) - (result externref) + (result (ref extern)) ) (export "fromCharCode" (func 4)) (func (import "wasm:js-string" "fromCodePoint") (param i32) - (result externref) + (result (ref extern)) ) (export "fromCodePoint" (func 5)) @@ -69,14 +69,14 @@ let testModule = `(module (func (import "wasm:js-string" "concat") (param externref externref) - (result externref) + (result (ref extern)) ) (export "concat" (func 9)) (func (import "wasm:js-string" "substring") (param externref i32 i32) - (result externref) + (result (ref extern)) ) (export "substring" (func 10)) diff --git a/js/src/jit-test/tests/wasm/exnref/casting.js b/js/src/jit-test/tests/wasm/exnref/casting.js index fa433fc152..20fbd145fe 100644 --- a/js/src/jit-test/tests/wasm/exnref/casting.js +++ b/js/src/jit-test/tests/wasm/exnref/casting.js @@ -1,110 +1,199 @@ // |jit-test| skip-if: !wasmGcEnabled() -const { - refCast, - refTest, - branch, - branchFail, - refCastNullable, - refTestNullable, - branchNullable, - branchFailNullable, -} = wasmEvalText(`(module - (tag $a) - (func $make (param $null i32) (result exnref) - (if (local.get $null) - (then - (return (ref.null exn)) +// Test exnref +{ + const { + refCast, + refTest, + branch, + branchFail, + refCastNullable, + refTestNullable, + branchNullable, + branchFailNullable, + } = wasmEvalText(`(module + (tag $a) + (func $make (param $null i32) (result exnref) + (if (local.get $null) + (then + (return (ref.null exn)) + ) ) - ) - try_table (catch_all_ref 0) - throw $a - end - unreachable - ) + try_table (catch_all_ref 0) + throw $a + end + unreachable + ) - (func (export "refCast") (param $null i32) - (call $make (local.get $null)) - ref.cast (ref exn) - drop - ) - (func (export "refTest") (param $null i32) (result i32) - (call $make (local.get $null)) - ref.test (ref exn) - ) - (func (export "branch") (param $null i32) (result i32) - (block (result (ref exn)) + (func (export "refCast") (param $null i32) (call $make (local.get $null)) - br_on_cast 0 exnref (ref exn) + ref.cast (ref exn) drop - (return (i32.const 0)) ) - drop - (return (i32.const 1)) - ) - (func (export "branchFail") (param $null i32) (result i32) - (block (result exnref) + (func (export "refTest") (param $null i32) (result i32) (call $make (local.get $null)) - br_on_cast_fail 0 exnref (ref exn) + ref.test (ref exn) + ) + (func (export "branch") (param $null i32) (result i32) + (block (result (ref exn)) + (call $make (local.get $null)) + br_on_cast 0 exnref (ref exn) + drop + (return (i32.const 0)) + ) drop (return (i32.const 1)) ) - drop - (return (i32.const 0)) - ) + (func (export "branchFail") (param $null i32) (result i32) + (block (result exnref) + (call $make (local.get $null)) + br_on_cast_fail 0 exnref (ref exn) + drop + (return (i32.const 1)) + ) + drop + (return (i32.const 0)) + ) - (func (export "refCastNullable") (param $null i32) - (call $make (local.get $null)) - ref.cast exnref - drop - ) - (func (export "refTestNullable") (param $null i32) (result i32) - (call $make (local.get $null)) - ref.test exnref - ) - (func (export "branchNullable") (param $null i32) (result i32) - (block (result exnref) + (func (export "refCastNullable") (param $null i32) (call $make (local.get $null)) - br_on_cast 0 exnref exnref + ref.cast exnref drop - (return (i32.const 0)) ) - drop - (return (i32.const 1)) - ) - (func (export "branchFailNullable") (param $null i32) (result i32) - (block (result exnref) + (func (export "refTestNullable") (param $null i32) (result i32) (call $make (local.get $null)) - br_on_cast_fail 0 exnref exnref + ref.test exnref + ) + (func (export "branchNullable") (param $null i32) (result i32) + (block (result exnref) + (call $make (local.get $null)) + br_on_cast 0 exnref exnref + drop + (return (i32.const 0)) + ) drop (return (i32.const 1)) ) - drop - (return (i32.const 0)) - ) -)`).exports; + (func (export "branchFailNullable") (param $null i32) (result i32) + (block (result exnref) + (call $make (local.get $null)) + br_on_cast_fail 0 exnref exnref + drop + (return (i32.const 1)) + ) + drop + (return (i32.const 0)) + ) + )`).exports; -// cast non-null exnref -> (ref exn) -refCast(0); -assertEq(refTest(0), 1); -assertEq(branch(0), 1); -assertEq(branchFail(0), 1); + // cast non-null exnref -> (ref exn) + refCast(0); + assertEq(refTest(0), 1); + assertEq(branch(0), 1); + assertEq(branchFail(0), 1); -// cast non-null exnref -> exnref -refCastNullable(0); -assertEq(refTestNullable(0), 1); -assertEq(branchNullable(0), 1); -assertEq(branchFailNullable(0), 1); + // cast non-null exnref -> exnref + refCastNullable(0); + assertEq(refTestNullable(0), 1); + assertEq(branchNullable(0), 1); + assertEq(branchFailNullable(0), 1); + + // cast null exnref -> (ref exn) + assertErrorMessage(() => refCast(1), WebAssembly.RuntimeError, /bad cast/); + assertEq(refTest(1), 0); + assertEq(branch(1), 0); + assertEq(branchFail(1), 0); + + // cast null exnref -> exnref + refCastNullable(1); + assertEq(refTestNullable(1), 1); + assertEq(branchNullable(1), 1); + assertEq(branchFailNullable(1), 1); +} + + +// Test nullexnref +{ + const { + refCastNull, + refCastNonNull, + refTestNull, + refTestNonNull, + branchNull, + branchNonNull, + branchFailNull, + branchFailNonNull, + } = wasmEvalText(`(module + (func (export "refCastNull") + ref.null noexn + ref.cast nullexnref + drop + ) + (func (export "refCastNonNull") + ref.null noexn + ref.cast (ref noexn) + drop + ) + (func (export "refTestNull") (result i32) + ref.null noexn + ref.test nullexnref + ) + (func (export "refTestNonNull") (result i32) + ref.null noexn + ref.test (ref noexn) + ) + (func (export "branchNull") (result i32) + (block (result nullexnref) + ref.null noexn + br_on_cast 0 exnref nullexnref + drop + (return (i32.const 0)) + ) + drop + (return (i32.const 1)) + ) + (func (export "branchNonNull") (result i32) + (block (result (ref noexn)) + ref.null noexn + br_on_cast 0 exnref (ref noexn) + drop + (return (i32.const 0)) + ) + drop + (return (i32.const 1)) + ) + (func (export "branchFailNull") (result i32) + (block (result exnref) + ref.null noexn + br_on_cast_fail 0 exnref (ref noexn) + drop + (return (i32.const 1)) + ) + drop + (return (i32.const 0)) + ) + (func (export "branchFailNonNull") (result i32) + (block (result (ref exn)) + ref.null noexn + br_on_cast_fail 0 exnref (ref null noexn) + drop + (return (i32.const 1)) + ) + drop + (return (i32.const 0)) + ) + )`).exports; -// cast null exnref -> (ref exn) -assertErrorMessage(() => refCast(1), WebAssembly.RuntimeError, /bad cast/); -assertEq(refTest(1), 0); -assertEq(branch(1), 0); -assertEq(branchFail(1), 0); + // null exceptions can be casted to nullexnref + refCastNull(); + assertEq(refTestNull(), 1); + assertEq(branchNull(), 1); + assertEq(branchFailNull(), 0); -// cast null exnref -> exnref -refCastNullable(1); -assertEq(refTestNullable(1), 1); -assertEq(branchNullable(1), 1); -assertEq(branchFailNullable(1), 1); + // null exceptions cannot be casted to (ref noexn) + assertErrorMessage(() => refCastNonNull(), WebAssembly.RuntimeError, /bad cast/); + assertEq(refTestNonNull(), 0); + assertEq(branchNonNull(), 0); + assertEq(branchFailNonNull(), 1); +} diff --git a/js/src/jit-test/tests/wasm/exnref/try-table.js b/js/src/jit-test/tests/wasm/exnref/try-table.js index c89330917c..407d6db133 100644 --- a/js/src/jit-test/tests/wasm/exnref/try-table.js +++ b/js/src/jit-test/tests/wasm/exnref/try-table.js @@ -382,3 +382,56 @@ } } } + +// WebAssembly.JSTag property is read-only and enumerable +WebAssembly.JSTag = null; +assertEq(WebAssembly.JSTag !== null, true); +assertEq(WebAssembly.propertyIsEnumerable('JSTag'), true); + +// Test try_table catching JS exceptions and unpacking them using JSTag +{ + let tag = WebAssembly.JSTag; + let values = [...WasmExternrefValues]; + function throwJS(value) { + throw value; + } + let {test} = wasmEvalText(`(module + (import "" "tag" (tag $tag (param externref))) + (import "" "throwJS" (func $throwJS (param externref))) + (func (export "test") (param externref) (result externref) + try_table (catch $tag 0) + local.get 0 + call $throwJS + end + unreachable + ) + )`, {"": {tag, throwJS}}).exports; + + for (let value of values) { + assertEq(value, test(value)); + } +} + +// Test try_table catching JS exceptions using JSTag and unpacking them using JSTag +{ + let tag = WebAssembly.JSTag; + let values = [...WasmExternrefValues]; + function throwJS(value) { + throw new WebAssembly.Exception(tag, [value]); + } + let {test} = wasmEvalText(`(module + (import "" "tag" (tag $tag (param externref))) + (import "" "throwJS" (func $throwJS (param externref))) + (func (export "test") (param externref) (result externref) + try_table (catch $tag 0) + local.get 0 + call $throwJS + end + unreachable + ) + )`, {"": {tag, throwJS}}).exports; + + for (let value of values) { + assertEq(value, test(value)); + } +} diff --git a/js/src/jit-test/tests/wasm/js-promise-integration/basic.js b/js/src/jit-test/tests/wasm/js-promise-integration/basic.js new file mode 100644 index 0000000000..8603886b07 --- /dev/null +++ b/js/src/jit-test/tests/wasm/js-promise-integration/basic.js @@ -0,0 +1,147 @@ +// Example from the proposal. + +var compute_delta = async (i) => Promise.resolve(i / 100 || 1); + +var suspending_compute_delta = new WebAssembly.Function( + { parameters: ['externref', 'i32'], results: ['f64'] }, + compute_delta, + { suspending: "first" } +); + +var ins = wasmEvalText(`(module + (import "js" "init_state" (func $init_state (result f64))) + (import "js" "compute_delta" + (func $compute_delta_import (param externref) (param i32) (result f64))) + + (global $suspender (mut externref) (ref.null extern)) + (global $state (mut f64) (f64.const nan)) + (func $init (global.set $state (call $init_state))) + (start $init) + + (func $compute_delta (param i32) (result f64) + (local $suspender_copy externref) + (;return (f64.const 0.3);) + (;unreachable;) + (global.get $suspender) + (local.tee $suspender_copy) + (local.get 0) + (call $compute_delta_import) + (local.get $suspender_copy) + (global.set $suspender) + (return) + ) + (func $get_state (export "get_state") (result f64) (global.get $state)) + (func $update_state (param i32) (result f64) + (global.set $state (f64.add + (global.get $state) (call $compute_delta (local.get 0)))) + (global.get $state) + ) + + (func (export "update_state_export") + (param $suspender externref) (param i32) (result f64) + (local.get $suspender) + (global.set $suspender) + (local.get 1) + (call $update_state) + (return) + ) +)`, { + js: { + init_state() { return 0; }, + compute_delta: suspending_compute_delta, + }, +}); + +var update_state = new WebAssembly.Function( + { parameters: ['i32'], results: ['externref'] }, + ins.exports.update_state_export, + { promising: "first" } +); + +var res = update_state(4); +var tasks = res.then((r) => { + print(r); + assertEq(ins.exports.get_state(), .04); +}); + +assertEq(ins.exports.get_state(), 0); + +// Also test with exceptions/traps. + +async function test(c) { + var compute_delta = (i) => Promise.resolve(i/100 || 1); + if (c == 1) compute_delta = async (i) => {throw "ff"}; + if (c == 2) compute_delta = () => {throw "ff";} + + var suspending_compute_delta = new WebAssembly.Function( + {parameters:['externref', 'i32'], results:['f64']}, + compute_delta, + {suspending:"first"} + ); + + var ins = wasmEvalText(`(module + (import "js" "init_state" (func $init_state (result f64))) + (import "js" "compute_delta" + (func $compute_delta_import (param externref) (param i32) (result f64))) + + (global $suspender (mut externref) (ref.null extern)) + (global $state (mut f64) (f64.const nan)) + (func $init (global.set $state (call $init_state))) + (start $init) + + (func $compute_delta (param i32) (result f64) + (local $suspender_copy externref) + (;return (f64.const 0.3);) + ${c == 3 ? "(unreachable)" : ""} + (global.get $suspender) + (local.tee $suspender_copy) + (local.get 0) + (call $compute_delta_import) + ${c == 4 ? "(unreachable)" : ""} + (local.get $suspender_copy) + (global.set $suspender) + (return) + ) + (func $get_state (export "get_state") (result f64) (global.get $state)) + (func $update_state (param i32) (result f64) + (global.set $state (f64.add + (global.get $state) (call $compute_delta (local.get 0)))) + (global.get $state) + ) + + (func (export "update_state_export") + (param $suspender externref) (param i32) (result f64) + (local.get $suspender) + (global.set $suspender) + (local.get 1) + (call $update_state) + (return) + ) + )`, { + js: { + init_state() { return 0; }, + compute_delta: suspending_compute_delta, + }, + }); + + var update_state = new WebAssembly.Function( + {parameters:['i32'], results:['externref']}, + ins.exports.update_state_export, + {promising : "first"} + ); + + var res = update_state(4); + var p = res.then((r) => { + assertEq(c, 0); + assertEq(ins.exports.get_state(), .04); + }).catch(_ => { + assertEq(c > 0, true); + }); + + assertEq(ins.exports.get_state(), 0); + await p; +} + +for (let c = 0; c < 5; c++) { + tasks = tasks.then(() => test(c)); +} diff --git a/js/src/jit-test/tests/wasm/js-promise-integration/basic2.js b/js/src/jit-test/tests/wasm/js-promise-integration/basic2.js new file mode 100644 index 0000000000..d381951b2c --- /dev/null +++ b/js/src/jit-test/tests/wasm/js-promise-integration/basic2.js @@ -0,0 +1,97 @@ +// New API experiments. +// Example from the proposal. + +var compute_delta = async (i) => Promise.resolve(i / 100 || 1); + +var suspending_compute_delta = new WebAssembly.Suspending( + compute_delta +); +var ins = wasmEvalText(`(module + (import "js" "init_state" (func $init_state (result f64))) + (import "js" "compute_delta" + (func $compute_delta (param i32) (result f64))) + + (global $suspender (mut externref) (ref.null extern)) + (global $state (mut f64) (f64.const nan)) + (func $init (global.set $state (call $init_state))) + (start $init) + + (func $get_state (export "get_state") (result f64) (global.get $state)) + (func (export "update_state_export") (param i32) (result f64) + (global.set $state (f64.add + (global.get $state) (call $compute_delta (local.get 0)))) + (global.get $state) + ) +)`, { + js: { + init_state() { return 0; }, + compute_delta: suspending_compute_delta, + }, +}); + +var update_state = WebAssembly.promising( + ins.exports.update_state_export +); + +var res = update_state(4); +var tasks = res.then((r) => { + print(r); + assertEq(ins.exports.get_state(), .04); +}); + +assertEq(ins.exports.get_state(), 0); + +// Also test with exceptions/traps. + +async function test(c) { + var compute_delta = (i) => Promise.resolve(i/100 || 1); + if (c == 1) compute_delta = async (i) => {throw "ff"}; + if (c == 2) compute_delta = () => {throw "ff";} + + var suspending_compute_delta = new WebAssembly.Suspending( + compute_delta + ); + var ins = wasmEvalText(`(module + (import "js" "init_state" (func $init_state (result f64))) + (import "js" "compute_delta" + (func $compute_delta (param i32) (result f64))) + + (global $suspender (mut externref) (ref.null extern)) + (global $state (mut f64) (f64.const nan)) + (func $init (global.set $state (call $init_state))) + (start $init) + + (func $get_state (export "get_state") (result f64) (global.get $state)) + (func (export "update_state_export") (param i32) (result f64) + ${c == 3 ? "(unreachable)" : ""} + (global.set $state (f64.add + (global.get $state) (call $compute_delta (local.get 0)))) + ${c == 4 ? "(unreachable)" : ""} + (global.get $state) + ) + )`, { + js: { + init_state() { return 0; }, + compute_delta: suspending_compute_delta, + }, + }); + + var update_state = WebAssembly.promising( + ins.exports.update_state_export +); + + var res = update_state(4); + var p = res.then((r) => { + assertEq(c, 0); + assertEq(ins.exports.get_state(), .04); + }).catch(_ => { + assertEq(c > 0, true); + }); + + assertEq(ins.exports.get_state(), 0); + await p; +} + +for (let c = 0; c < 5; c++) { + tasks = tasks.then(() => test(c)); +} diff --git a/js/src/jit-test/tests/wasm/js-promise-integration/directives.txt b/js/src/jit-test/tests/wasm/js-promise-integration/directives.txt new file mode 100644 index 0000000000..b15152352a --- /dev/null +++ b/js/src/jit-test/tests/wasm/js-promise-integration/directives.txt @@ -0,0 +1 @@ +|jit-test| include:wasm.js; --setpref=wasm_js_promise_integration=true; skip-if: !wasmJSPromiseIntegrationEnabled() diff --git a/js/src/jit-test/tests/wasm/js-promise-integration/gc-2.js b/js/src/jit-test/tests/wasm/js-promise-integration/gc-2.js new file mode 100644 index 0000000000..d17539807e --- /dev/null +++ b/js/src/jit-test/tests/wasm/js-promise-integration/gc-2.js @@ -0,0 +1,69 @@ +// Example from the proposal. + +gczeal(2,5); + +var compute_delta = (i) => Promise.resolve(i / 100 || 1); + +var suspending_compute_delta = new WebAssembly.Function( + { parameters: ['externref', 'i32'], results: ['f64'] }, + compute_delta, + { suspending: "first" } +); + +var ins = wasmEvalText(`(module + (import "js" "init_state" (func $init_state (result f64))) + (import "js" "compute_delta" + (func $compute_delta_import (param externref) (param i32) (result f64))) + + (global $suspender (mut externref) (ref.null extern)) + (global $state (mut f64) (f64.const nan)) + (func $init (global.set $state (call $init_state))) + (start $init) + + (func $compute_delta (param i32) (result f64) + (local $suspender_copy externref) + (;return (f64.const 0.3);) + (;unreachable;) + (global.get $suspender) + (local.tee $suspender_copy) + (local.get 0) + (call $compute_delta_import) + (local.get $suspender_copy) + (global.set $suspender) + (return) + ) + (func $get_state (export "get_state") (result f64) (global.get $state)) + (func $update_state (param i32) (result f64) + (global.set $state (f64.add + (global.get $state) (call $compute_delta (local.get 0)))) + (global.get $state) + ) + + (func (export "update_state_export") + (param $suspender externref) (param i32) (result f64) + (local.get $suspender) + (global.set $suspender) + (local.get 1) + (call $update_state) + (return) + ) +)`, { + js: { + init_state() { return 0; }, + compute_delta: suspending_compute_delta, + }, +}); + +var update_state = new WebAssembly.Function( + { parameters: ['i32'], results: ['externref'] }, + ins.exports.update_state_export, + { promising: "first" } +); + +var res = update_state(4); +var tasks = res.then((r) => { + print(r); + assertEq(ins.exports.get_state(), .04); +}); + +assertEq(ins.exports.get_state(), 0); diff --git a/js/src/jit-test/tests/wasm/js-promise-integration/gc.js b/js/src/jit-test/tests/wasm/js-promise-integration/gc.js new file mode 100644 index 0000000000..c688b99cb5 --- /dev/null +++ b/js/src/jit-test/tests/wasm/js-promise-integration/gc.js @@ -0,0 +1,67 @@ +// Test if we can trace roots on the alternative stack. + +let i = 0; +function js_import() { + return Promise.resolve({i: ++i}); +}; +let wasm_js_import = new WebAssembly.Function( + {parameters: ['externref'], results: ['externref']}, + js_import, + {suspending: 'first'}); + +let wasm_gc_import = new WebAssembly.Function( + {parameters: ['externref'], results: []}, + async () => { gc(); }, + {suspending: 'first'}); + +var ins = wasmEvalText(`(module + (import "m" "import" + (func (param externref) (result externref))) + (import "m" "gc" (func (param externref))) + (import "m" "conv" + (func (param externref) (result i32))) + + (global (export "g") (mut i32) (i32.const 0)) + + (func (export "test") (param externref) + (local i32) + i32.const 5 + local.set 1 + loop + local.get 0 + call 0 + local.get 0 + call 1 + call 2 + global.get 0 + i32.add + global.set 0 + local.get 1 + i32.const 1 + i32.sub + local.tee 1 + br_if 0 + end + ) + +)`, { + m: { + import: wasm_js_import, + gc: wasm_gc_import, + conv: ({i}) => i, + }, +}); + + +let wrapped_export = new WebAssembly.Function( + {parameters:[], results:['externref']}, + ins.exports.test, + {promising : "first"} +); + +let export_promise = wrapped_export(); +assertEq(0, ins.exports.g.value); +assertEq(true, export_promise instanceof Promise); +export_promise.then(() => + assertEq(15, ins.exports.g.value) +); diff --git a/js/src/jit-test/tests/wasm/js-promise-integration/js-promise-integration.new.js b/js/src/jit-test/tests/wasm/js-promise-integration/js-promise-integration.new.js new file mode 100644 index 0000000000..d45d47b072 --- /dev/null +++ b/js/src/jit-test/tests/wasm/js-promise-integration/js-promise-integration.new.js @@ -0,0 +1,270 @@ +// New version of JS promise integration API +// Modified https://github.com/WebAssembly/js-promise-integration/tree/main/test/js-api/js-promise-integration + +var tests = Promise.resolve(); +function test(fn, n) { + + tests = tests.then(() => { + let t = {res: null}; + print("# " + n); + fn(t); + return t.res; + }); +} +function promise_test(fn, n) { + tests = tests.then(() => { + print("# " + n); + return fn(); + }); +} +function assert_true(f) { assertEq(f, true); } +function assert_equals(a, b) { assertEq(a, b); } +function assert_array_equals(a, b) { + assert_equals(a.length, a.length); + for (let i = 0; i < a.length; i++) { + assert_equals(a[i], b[i]); + } +} +function assert_throws(ex, fn) { + try { + fn(); assertEq(false, true); + } catch(e) { + assertEq(e instanceof ex, true); + } +} +function promise_rejects(t, obj, p) { + t.res = p.then(() => { + assertEq(true, false); + }, (e) => { + assertEq(e instanceof obj.constructor, true); + }); +} + +function ToPromising(wasm_export) { + let sig = wasm_export.type(); + assert_true(sig.parameters.length > 0); + assert_equals('externref', sig.parameters[0]); + let wrapper_sig = { + parameters: sig.parameters.slice(1), + results: ['externref'] + }; + return new WebAssembly.Function( + wrapper_sig, wasm_export, {promising: 'first'}); +} + +promise_test(async () => { + let js_import = new WebAssembly.Suspending( + () => Promise.resolve(42) + ); + let instance = wasmEvalText(`(module + (import "m" "import" (func $import (result i32))) + (func (export "test") (result i32) + call $import + ) + )`, {m: {import: js_import}}); + let wrapped_export = WebAssembly.promising(instance.exports.test); + let export_promise = wrapped_export(); + assert_true(export_promise instanceof Promise); + assert_equals(42, await export_promise); +}, "Suspend once"); + +promise_test(async () => { + let i = 0; + function js_import() { + return Promise.resolve(++i); + }; + let wasm_js_import = new WebAssembly.Suspending(js_import); + // void test() { + // for (i = 0; i < 5; ++i) { + // g = g + await import(); + // } + // } + let instance = wasmEvalText(`(module + (import "m" "import" (func $import (param externref) (result i32))) + (global (export "g") (mut i32) (i32.const 0)) + (func (export "test") (param externref) + (local i32) + i32.const 5 + local.set 1 + loop + local.get 0 + call $import + global.get 0 + i32.add + global.set 0 + local.get 1 + i32.const 1 + i32.sub + local.tee 1 + br_if 0 + end + ) + )`, {m: {import: wasm_js_import}}); + let wrapped_export = WebAssembly.promising(instance.exports.test); + let export_promise = wrapped_export(); + assert_equals(0, instance.exports.g.value); + assert_true(export_promise instanceof Promise); + await export_promise; + assert_equals(15, instance.exports.g.value); +}, "Suspend/resume in a loop"); + +promise_test(async () => { + function js_import() { + return 42 + }; + let wasm_js_import = new WebAssembly.Suspending(js_import); + let instance = wasmEvalText(`(module + (import "m" "import" (func $import (param externref) (result i32))) + (global (export "g") (mut i32) (i32.const 0)) + (func (export "test") (param externref) (result i32) + local.get 0 + call $import + global.set 0 + global.get 0 + ) + )`, {m: {import: wasm_js_import}}); + let wrapped_export = WebAssembly.promising(instance.exports.test); + await wrapped_export(); + assert_equals(42, instance.exports.g.value); +}, "Do not suspend if the import's return value is not a Promise"); + +test(t => { + let tag = new WebAssembly.Tag({parameters: []}); + function js_import() { + return Promise.resolve(); + }; + let wasm_js_import = new WebAssembly.Suspending(js_import); + function js_throw() { + throw new Error(); + } + + let instance = wasmEvalText(`(module + (import "m" "import" (func $import (param externref) (result i32))) + (import "m" "js_throw" (func $js_throw)) + (func (export "test") (param externref) (result i32) + local.get 0 + call $import + call $js_throw + ) + )`, {m: {import: wasm_js_import, js_throw}}); + let wrapped_export = WebAssembly.promising(instance.exports.test); + let export_promise = wrapped_export(); + assert_true(export_promise instanceof Promise); + promise_rejects(t, new Error(), export_promise); +}, "Throw after the first suspension"); + +// TODO: Use wasm exception handling to check that the exception can be caught in wasm. + +test(t => { + let tag = new WebAssembly.Tag({parameters: ['i32']}); + function js_import() { + return Promise.reject(new Error()); + }; + let wasm_js_import = new WebAssembly.Suspending(js_import); + + let instance = wasmEvalText(`(module + (import "m" "import" (func $import (param externref) (result i32))) + (func (export "test") (param externref) (result i32) + local.get 0 + call $import + ) + )`, {m: {import: wasm_js_import, tag: tag}}); + let wrapped_export = WebAssembly.promising(instance.exports.test); + let export_promise = wrapped_export(); + assert_true(export_promise instanceof Promise); + promise_rejects(t, new Error(), export_promise); +}, "Rejecting promise"); + +async function TestNestedSuspenders(suspend) { + // Nest two suspenders. The call chain looks like: + // outer (wasm) -> outer (js) -> inner (wasm) -> inner (js) + // If 'suspend' is true, the inner JS function returns a Promise, which + // suspends the inner wasm function, which returns a Promise, which suspends + // the outer wasm function, which returns a Promise. The inner Promise + // resolves first, which resumes the inner continuation. Then the outer + // promise resolves which resumes the outer continuation. + // If 'suspend' is false, the inner and outer JS functions return a regular + // value and no computation is suspended. + + let inner = new WebAssembly.Suspending( + () => suspend ? Promise.resolve(42) : 43, + ); + + let export_inner; + let outer = new WebAssembly.Suspending( + () => suspend ? export_inner() : 42, + ); + + let instance = wasmEvalText(`(module + (import "m" "inner" (func $inner (param externref) (result i32))) + (import "m" "outer" (func $outer (param externref) (result i32))) + (func (export "outer") (param externref) (result i32) + local.get 0 + call $outer + ) + (func (export "inner") (param externref) (result i32) + local.get 0 + call $inner + ) + )`, {m: {inner, outer}}); + export_inner = WebAssembly.promising(instance.exports.inner); + let export_outer = WebAssembly.promising(instance.exports.outer); + let result = export_outer(); + assert_true(result instanceof Promise); + assert_equals(42, await result); +} + +promise_test(async () => { + return TestNestedSuspenders(true); +}, "Test nested suspenders with suspension"); + +promise_test(async () => { + return TestNestedSuspenders(false); +}, "Test nested suspenders with no suspension"); + + +test(t => { + let instance = wasmEvalText(`(module + (func (export "test") (result i32) + call 0 + ) + )`); + let wrapper = WebAssembly.promising(instance.exports.test); + promise_rejects(t, new InternalError(), wrapper()); +}, "Stack overflow"); + +// TODO: Test suspension with funcref. + +test(t => { + // The call stack of this test looks like: + // export1 -> import1 -> export2 -> import2 + // Where export1 is "promising" and import2 is "suspending". Returning a + // promise from import2 should trap because of the JS import in the middle. + let instance; + function import1() { + // import1 -> export2 (unwrapped) + instance.exports.export2(); + } + function import2() { + return Promise.resolve(0); + } + import2 = new WebAssembly.Suspending(import2); + instance = wasmEvalText(`(module + (import "m" "import1" (func $import1 (result i32))) + (import "m" "import2" (func $import2 (result i32))) + (func (export "export1") (result i32) + ;; export1 -> import1 (unwrapped) + call $import1 + ) + (func (export "export2") (result i32) + ;; export2 -> import2 (suspending) + call $import2 + ) + )`, + {'m': {'import1': import1, 'import2': import2}}); + // export1 (promising) + let wrapper = WebAssembly.promising(instance.exports.export1); + promise_rejects(t, new WebAssembly.RuntimeError(), wrapper()); +}, "Test that trying to suspend JS frames traps"); + +tests.then(() => print('Done')); diff --git a/js/src/jit-test/tests/wasm/js-promise-integration/js-promise-integration.old.js b/js/src/jit-test/tests/wasm/js-promise-integration/js-promise-integration.old.js new file mode 100644 index 0000000000..cc743eeaaa --- /dev/null +++ b/js/src/jit-test/tests/wasm/js-promise-integration/js-promise-integration.old.js @@ -0,0 +1,406 @@ +// Old version of JS promise integration API +// Modified https://github.com/WebAssembly/js-promise-integration/tree/main/test/js-api/js-promise-integration + +var tests = Promise.resolve(); +function test(fn, n) { + + tests = tests.then(() => { + let t = {res: null}; + print("# " + n); + fn(t); + return t.res; + }); +} +function promise_test(fn, n) { + tests = tests.then(() => { + print("# " + n); + return fn(); + }); +} +function assert_true(f) { assertEq(f, true); } +function assert_equals(a, b) { assertEq(a, b); } +function assert_array_equals(a, b) { + assert_equals(a.length, a.length); + for (let i = 0; i < a.length; i++) { + assert_equals(a[i], b[i]); + } +} +function assert_throws(ex, fn) { + try { + fn(); assertEq(false, true); + } catch(e) { + assertEq(e instanceof ex, true); + } +} +function promise_rejects(t, obj, p) { + t.res = p.then(() => { + assertEq(true, false); + }, (e) => { + assertEq(e instanceof obj.constructor, true); + }); +} + +function ToPromising(wasm_export) { + let sig = wasm_export.type(); + assert_true(sig.parameters.length > 0); + assert_equals('externref', sig.parameters[0]); + let wrapper_sig = { + parameters: sig.parameters.slice(1), + results: ['externref'] + }; + return new WebAssembly.Function( + wrapper_sig, wasm_export, {promising: 'first'}); +} + +test(() => { + function js_import(i) {} + + let import_wrapper = new WebAssembly.Function( + {parameters: ['externref', 'i32'], results: []}, + js_import, + {suspending: 'first'}); + let instance = wasmEvalText(`(module + (import "m" "import" (func (param externref i32))) + (func (export "export") (param externref i32) (result i32) + local.get 1 + ) + (func (export "void_export") (param externref)) + )`, {'m': {'import': import_wrapper}}); + let export_wrapper = ToPromising(instance.exports.export); + + // Bad flag value. + assert_throws(TypeError, () => new WebAssembly.Function( + {parameters: ['externref', 'i32'], results: []}, + js_import, + {suspending: 'foo'})); + + assert_throws(TypeError, () => new WebAssembly.Function( + {parameters: ['i32'], results: ['externref']}, + instance.exports.export, + {promising: 'foo'})); + + // Signature mismatch. + assert_throws(Error /*TypeError*/, () => new WebAssembly.Function( + {parameters: ['externref'], results: []}, + new WebAssembly.Function( + {parameters: [], results: ['i32']}, js_import), + {suspending: 'first'})); + + assert_throws(TypeError, () => new WebAssembly.Function( + {parameters: ['externref', 'i32'], results: ['i32']}, + instance.exports.export, + {promising: 'first'})); + + // Check the wrapper signatures. + // let export_sig = export_wrapper.type(); + // assert_array_equals(['i32'], export_sig.parameters); + // assert_array_equals(['externref'], export_sig.results); + + let import_sig = import_wrapper.type(); + assert_array_equals(['externref', 'i32'], import_sig.parameters); + assert_array_equals([], import_sig.results); + // let void_export_wrapper = ToPromising(instance.exports.void_export); + // let void_export_sig = void_export_wrapper.type(); + // assert_array_equals([], void_export_sig.parameters); + // assert_array_equals(['externref'], void_export_sig.results); +}, "Test import and export type checking"); + +promise_test(async () => { + let js_import = new WebAssembly.Function( + {parameters: ['externref'], results: ['i32']}, + () => Promise.resolve(42), + {suspending: 'first'}); + let instance = wasmEvalText(`(module + (import "m" "import" (func $import (param externref) (result i32))) + (func (export "test") (param externref) (result i32) + local.get 0 + call $import + ) + )`, {m: {import: js_import}}); + let wrapped_export = ToPromising(instance.exports.test); + let export_promise = wrapped_export(); + assert_true(export_promise instanceof Promise); + assert_equals(42, await export_promise); +}, "Suspend once"); + +promise_test(async () => { + let i = 0; + function js_import() { + return Promise.resolve(++i); + }; + let wasm_js_import = new WebAssembly.Function( + {parameters: ['externref'], results: ['i32']}, + js_import, + {suspending: 'first'}); + // void test() { + // for (i = 0; i < 5; ++i) { + // g = g + await import(); + // } + // } + let instance = wasmEvalText(`(module + (import "m" "import" (func $import (param externref) (result i32))) + (global (export "g") (mut i32) (i32.const 0)) + (func (export "test") (param externref) + (local i32) + i32.const 5 + local.set 1 + loop + local.get 0 + call $import + global.get 0 + i32.add + global.set 0 + local.get 1 + i32.const 1 + i32.sub + local.tee 1 + br_if 0 + end + ) + )`, {m: {import: wasm_js_import}}); + let wrapped_export = ToPromising(instance.exports.test); + let export_promise = wrapped_export(); + assert_equals(0, instance.exports.g.value); + assert_true(export_promise instanceof Promise); + await export_promise; + assert_equals(15, instance.exports.g.value); +}, "Suspend/resume in a loop"); + +promise_test(async () => { + function js_import() { + return 42 + }; + let wasm_js_import = new WebAssembly.Function( + {parameters: ['externref'], results: ['i32']}, + js_import, + {suspending: 'first'}); + let instance = wasmEvalText(`(module + (import "m" "import" (func $import (param externref) (result i32))) + (global (export "g") (mut i32) (i32.const 0)) + (func (export "test") (param externref) (result i32) + local.get 0 + call $import + global.set 0 + global.get 0 + ) + )`, {m: {import: wasm_js_import}}); + let wrapped_export = ToPromising(instance.exports.test); + await wrapped_export(); + assert_equals(42, instance.exports.g.value); +}, "Do not suspend if the import's return value is not a Promise"); + +test(t => { + let tag = new WebAssembly.Tag({parameters: []}); + function js_import() { + return Promise.resolve(); + }; + let wasm_js_import = new WebAssembly.Function( + {parameters: ['externref'], results: ['i32']}, + js_import, + {suspending: 'first'}); + function js_throw() { + throw new Error(); + } + + let instance = wasmEvalText(`(module + (import "m" "import" (func $import (param externref) (result i32))) + (import "m" "js_throw" (func $js_throw)) + (func (export "test") (param externref) (result i32) + local.get 0 + call $import + call $js_throw + ) + )`, {m: {import: wasm_js_import, js_throw}}); + let wrapped_export = ToPromising(instance.exports.test); + let export_promise = wrapped_export(); + assert_true(export_promise instanceof Promise); + promise_rejects(t, new Error(), export_promise); +}, "Throw after the first suspension"); + +// TODO: Use wasm exception handling to check that the exception can be caught in wasm. + +test(t => { + let tag = new WebAssembly.Tag({parameters: ['i32']}); + function js_import() { + return Promise.reject(new Error()); + }; + let wasm_js_import = new WebAssembly.Function( + {parameters: ['externref'], results: ['i32']}, + js_import, + {suspending: 'first'}); + + let instance = wasmEvalText(`(module + (import "m" "import" (func $import (param externref) (result i32))) + (func (export "test") (param externref) (result i32) + local.get 0 + call $import + ) + )`, {m: {import: wasm_js_import, tag: tag}}); + let wrapped_export = ToPromising(instance.exports.test); + let export_promise = wrapped_export(); + assert_true(export_promise instanceof Promise); + promise_rejects(t, new Error(), export_promise); +}, "Rejecting promise"); + +async function TestNestedSuspenders(suspend) { + // Nest two suspenders. The call chain looks like: + // outer (wasm) -> outer (js) -> inner (wasm) -> inner (js) + // If 'suspend' is true, the inner JS function returns a Promise, which + // suspends the inner wasm function, which returns a Promise, which suspends + // the outer wasm function, which returns a Promise. The inner Promise + // resolves first, which resumes the inner continuation. Then the outer + // promise resolves which resumes the outer continuation. + // If 'suspend' is false, the inner and outer JS functions return a regular + // value and no computation is suspended. + + let inner = new WebAssembly.Function( + {parameters: ['externref'], results: ['i32']}, + () => suspend ? Promise.resolve(42) : 43, + {suspending: 'first'}); + + let export_inner; + let outer = new WebAssembly.Function( + {parameters: ['externref'], results: ['i32']}, + () => suspend ? export_inner() : 42, + {suspending: 'first'}); + + let instance = wasmEvalText(`(module + (import "m" "inner" (func $inner (param externref) (result i32))) + (import "m" "outer" (func $outer (param externref) (result i32))) + (func (export "outer") (param externref) (result i32) + local.get 0 + call $outer + ) + (func (export "inner") (param externref) (result i32) + local.get 0 + call $inner + ) + )`, {m: {inner, outer}}); + export_inner = ToPromising(instance.exports.inner); + let export_outer = ToPromising(instance.exports.outer); + let result = export_outer(); + assert_true(result instanceof Promise); + assert_equals(42, await result); +} + +promise_test(async () => { + return TestNestedSuspenders(true); +}, "Test nested suspenders with suspension"); + +promise_test(async () => { + return TestNestedSuspenders(false); +}, "Test nested suspenders with no suspension"); + +test(() => { + let js_import = new WebAssembly.Function( + {parameters: ['externref'], results: ['i32']}, + () => Promise.resolve(42), + {suspending: 'first'}); + let instance = wasmEvalText(`(module + (import "m" "import" (func $import (param externref) (result i32))) + (func (export "test") (param externref) (result i32) + local.get 0 + call $import + ) + (func (export "return_suspender") (param externref) (result externref) + local.get 0 + ) + )`, {m: {import: js_import}}); + let suspender = ToPromising(instance.exports.return_suspender)(); + for (s of [suspender, null, undefined, {}]) { + assert_throws(WebAssembly.RuntimeError, () => instance.exports.test(s)); + } +}, "Call import with an invalid suspender"); + +test(t => { + let instance = wasmEvalText(`(module + (func (export "test") (param externref) (result i32) + local.get 0 + call 0 + ) + )`); + let wrapper = ToPromising(instance.exports.test); + promise_rejects(t, new InternalError(), wrapper()); +}, "Stack overflow"); + +test (() => { + let js_import = new WebAssembly.Function( + {parameters: ['externref'], results: ['i32']}, + () => Promise.resolve(42), + {suspending: 'first'}); + let instance = wasmEvalText(`(module + (import "m" "import" (func $import (param externref) (result i32))) + (func (export "test") (param externref) (result i32) + local.get 0 + call $import + ) + (func (export "return_suspender") (param externref) (result externref) + local.get 0 + ) + )`, {m: {import: js_import}}); + let suspender = ToPromising(instance.exports.return_suspender)(); + for (s of [suspender, null, undefined, {}]) { + assert_throws(WebAssembly.RuntimeError, () => instance.exports.test(s)); + } +}, "Pass an invalid suspender"); + +// TODO: Test suspension with funcref. + +test(t => { + // The call stack of this test looks like: + // export1 -> import1 -> export2 -> import2 + // Where export1 is "promising" and import2 is "suspending". Returning a + // promise from import2 should trap because of the JS import in the middle. + let instance; + function import1() { + // import1 -> export2 (unwrapped) + instance.exports.export2(); + } + function import2() { + return Promise.resolve(0); + } + import2 = new WebAssembly.Function( + {parameters: ['externref'], results: ['i32']}, + import2, + {suspending: 'first'}); + instance = wasmEvalText(`(module + (import "m" "import1" (func $import1 (result i32))) + (import "m" "import2" (func $import2 (param externref) (result i32))) + (global (mut externref) (ref.null extern)) + (func (export "export1") (param externref) (result i32) + ;; export1 -> import1 (unwrapped) + local.get 0 + global.set 0 + call $import1 + ) + (func (export "export2") (result i32) + ;; export2 -> import2 (suspending) + global.get 0 + call $import2 + ) + )`, + {'m': {'import1': import1, 'import2': import2}}); + // export1 (promising) + let wrapper = new WebAssembly.Function( + {parameters: [], results: ['externref']}, + instance.exports.export1, + {promising: 'first'}); + promise_rejects(t, new WebAssembly.RuntimeError(), wrapper()); +}, "Test that trying to suspend JS frames traps"); + +"Invalid test. Skipping..." || test(() => { + let js_import = new WebAssembly.Function( + {parameters: ['externref'], results: ['i32']}, + () => 42, + {suspending: 'first'}); + let instance = wasmEvalText(`(module + (import "m" "import" (func $import (param externref) (result i32))) + (func (export "test") (param externref) (result i32) + local.get 0 + call $import + ) + )`, {m: {import: js_import}}); + assert_equals(42, instance.exports.test(null)); +}, "Pass an invalid suspender to the import and return a non-promise"); + +tests.then(() => print('Done')); diff --git a/js/src/jit-test/tests/wasm/js-promise-integration/multi.js b/js/src/jit-test/tests/wasm/js-promise-integration/multi.js new file mode 100644 index 0000000000..7713637642 --- /dev/null +++ b/js/src/jit-test/tests/wasm/js-promise-integration/multi.js @@ -0,0 +1,36 @@ +// Multiple promises at the same time. + +function js_import() { + return Promise.resolve(42); +} +var wasm_js_import = new WebAssembly.Function( + { parameters: ['externref'], results: ['i32'] }, + js_import, + { suspending: 'first' }); + +var ins = wasmEvalText(`(module + (import "m" "import" (func $f (param externref) (result i32))) + (func (export "test") (param externref) (result i32) + local.get 0 + call $f + ) +)`, {"m": {import: wasm_js_import}}); + +let wrapped_export = new WebAssembly.Function( + { + parameters: [], + results: ['externref'] + }, + ins.exports.test, { promising: 'first' }); + +Promise.resolve().then(() => { + wrapped_export().then(i => { + assertEq(42, i) + }); +}); + +Promise.resolve().then(() => { + wrapped_export().then(i => { + assertEq(42, i) + }); +}); diff --git a/js/src/jit-test/tests/wasm/regress/bug1866545.js b/js/src/jit-test/tests/wasm/regress/bug1866545.js new file mode 100644 index 0000000000..6c6a92c0ab --- /dev/null +++ b/js/src/jit-test/tests/wasm/regress/bug1866545.js @@ -0,0 +1,25 @@ +// Tests stack alignment during tail calls (in Ion). + +var ins = wasmEvalText(` + (module + (func $trap + (param $i i32) (param $arr i32) + unreachable + ) + (func $second + (return_call $trap + (i32.const 33) + (i32.const 66) + ) + ) + (func (export "test") + (call $second) + ) + ) +`); + +assertErrorMessage( + () => ins.exports.test(), + WebAssembly.RuntimeError, + "unreachable executed", +); diff --git a/js/src/jit-test/tests/wasm/regress/bug1891658.js b/js/src/jit-test/tests/wasm/regress/bug1891658.js new file mode 100644 index 0000000000..304ea0f5ea --- /dev/null +++ b/js/src/jit-test/tests/wasm/regress/bug1891658.js @@ -0,0 +1,10 @@ +// Tests OOM during wasmLosslessInvoke. + +var ins = wasmEvalText('(module (func (export "f")(result i32) i32.const 1))'); + +oomAtAllocation(1); +try { + wasmLosslessInvoke(ins.exports.f); +} catch (e) { + assertEq(e, "out of memory"); +} diff --git a/js/src/jit-test/tests/wasm/tail-calls/bug1891422.js b/js/src/jit-test/tests/wasm/tail-calls/bug1891422.js new file mode 100644 index 0000000000..c5c43d006f --- /dev/null +++ b/js/src/jit-test/tests/wasm/tail-calls/bug1891422.js @@ -0,0 +1,27 @@ +// |jit-test| --more-compartments; skip-variant-if: --setpref=wasm_test_serialization=true, true; skip-variant-if: --wasm-compiler=ion, true + +a = newGlobal({ newCompartment: true }); +a.b = this; +a.eval(`Debugger(b).onExceptionUnwind = function () {};`); + +var ins0 = wasmEvalText(`(module + (func $fac-acc (export "e") (param i64 i64) + unreachable + ) +)`); +var ins = wasmEvalText(`(module + (import "" "e" (func $fac-acc (param i64 i64))) + (type $tz (func (param i64))) + (table $t 1 1 funcref) + (func $f (export "fac") (param i64) + local.get 0 + i32.const 0 + return_call_indirect $t (type $tz) + ) + (elem $t (i32.const 0) $fac-acc) +)`, {"": {e: ins0.exports.e}}); + + +assertErrorMessage(() => { + ins.exports.fac(5n); +}, WebAssembly.RuntimeError, /indirect call signature mismatch/); diff --git a/js/src/jit-test/tests/wasm/testing/bug1894586.js b/js/src/jit-test/tests/wasm/testing/bug1894586.js new file mode 100644 index 0000000000..eb19cc0a69 --- /dev/null +++ b/js/src/jit-test/tests/wasm/testing/bug1894586.js @@ -0,0 +1,13 @@ +var it = 100; +function f() { + if (--it < 0) { + return; + } + wasmDumpIon( + wasmTextToBinary( + "(type $x (struct))(global $g (ref null $x) ref.null $x)(func $h)" + ) + ); + oomTest(f); +} +f(); |