diff options
Diffstat (limited to 'js/src/jit-test/tests/warp')
196 files changed, 8653 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/warp/arguments-object-load-arg.js b/js/src/jit-test/tests/warp/arguments-object-load-arg.js new file mode 100644 index 0000000000..0598c3b1d6 --- /dev/null +++ b/js/src/jit-test/tests/warp/arguments-object-load-arg.js @@ -0,0 +1,109 @@ +// Test transpiling of LoadArgumentsObjectArgResult and cover all possible bailout conditions. + +function blackhole() { + // Direct eval prevents any compile-time optimisations. + eval(""); +} + +function testConstantArgAccess() { + blackhole(arguments); // Create an arguments object. + + for (var i = 0; i < 50; ++i) { + assertEq(arguments[0], 1); + } +} +for (var i = 0; i < 20; ++i) testConstantArgAccess(1); + +function testDynamicArgAccess() { + blackhole(arguments); // Create an arguments object. + + for (var i = 0; i < 50; ++i) { + assertEq(arguments[i & 1], 1 + (i & 1)); + } +} +for (var i = 0; i < 20; ++i) testDynamicArgAccess(1, 2); + +function markElementOveriddenIf(args, cond, value) { + with ({}) ; // Don't Warp compile to avoid cold code bailouts. + if (cond) { + Object.defineProperty(args, 0, {value}); + } +} + +function testBailoutElementReified() { + blackhole(arguments); // Create an arguments object. + + for (var i = 0; i < 50; ++i) { + markElementOveriddenIf(arguments, i === 25, 2); + + var expected = 1 + (i >= 25); + assertEq(arguments[0], expected); + } +} +for (var i = 0; i < 20; ++i) testBailoutElementReified(1); + +function markLengthOveriddenIf(args, cond, value) { + with ({}) ; // Don't Warp compile to avoid cold code bailouts. + if (cond) { + args.length = value; + } +} + +function testBailoutLengthReified() { + blackhole(arguments); // Create an arguments object. + + for (var i = 0; i < 50; ++i) { + markLengthOveriddenIf(arguments, i === 25, 0); + + assertEq(arguments[0], 1); + } +} +for (var i = 0; i < 20; ++i) testBailoutLengthReified(1); + +function deleteElementIf(args, cond) { + with ({}) ; // Don't Warp compile to avoid cold code bailouts. + if (cond) { + delete args[0]; + } +} + +function testBailoutElementDeleted() { + blackhole(arguments); // Create an arguments object. + + // Load expected values from an array to avoid possible cold code bailouts. + var values = [1, undefined]; + + for (var i = 0; i < 50; ++i) { + deleteElementIf(arguments, i === 25); + + var expected = values[0 + (i >= 25)]; + assertEq(arguments[0], expected); + } +} +for (var i = 0; i < 20; ++i) testBailoutElementDeleted(1); + +function testBailoutOutOfBounds() { + blackhole(arguments); // Create an arguments object. + + // Load expected values from an array to avoid possible cold code bailouts. + var values = [1, undefined]; + + for (var i = 0; i < 50; ++i) { + var index = 0 + (i >= 25); + var expected = values[index]; + assertEq(arguments[index], expected); + } +} +for (var i = 0; i < 20; ++i) testBailoutOutOfBounds(1); + +function testBailoutArgForwarded(arg1, arg2) { + blackhole(arguments); // Create an arguments object. + blackhole(() => arg2); // Ensure |arg2| is marked as "forwarded". + + for (var i = 0; i < 50; ++i) { + var index = 0 + (i >= 25); + var expected = 1 + (i >= 25); + assertEq(arguments[index], expected); + } +} +for (var i = 0; i < 20; ++i) testBailoutArgForwarded(1, 2); diff --git a/js/src/jit-test/tests/warp/arguments-object-load-length.js b/js/src/jit-test/tests/warp/arguments-object-load-length.js new file mode 100644 index 0000000000..935c406ac0 --- /dev/null +++ b/js/src/jit-test/tests/warp/arguments-object-load-length.js @@ -0,0 +1,57 @@ +// Test transpiling of LoadArgumentsObjectLengthResult and cover all possible bailout conditions. + +function blackhole() { + // Direct eval prevents any compile-time optimisations. + eval(""); +} + +function testLengthAccess() { + blackhole(arguments); // Create an arguments object. + + for (var i = 0; i < 50; ++i) { + assertEq(arguments.length, 1); + } +} +for (var i = 0; i < 20; ++i) testLengthAccess(1); + +function markLengthOveriddenIf(args, cond, value) { + with ({}) ; // Don't Warp compile to avoid cold code bailouts. + if (cond) { + args.length = value; + } +} + +function testBailoutLengthReified() { + blackhole(arguments); // Create an arguments object. + + for (var i = 0; i < 50; ++i) { + markLengthOveriddenIf(arguments, i === 25, 0); + + var expected = 0 + (i < 25); + assertEq(arguments.length, expected); + } +} +for (var i = 0; i < 20; ++i) testBailoutLengthReified(1); + + +function deleteLengthIf(args, cond) { + with ({}) ; // Don't Warp compile to avoid cold code bailouts. + if (cond) { + delete args.length; + } +} + +function testBailoutLengthDeleted() { + blackhole(arguments); // Create an arguments object. + + // Load expected values from an array to avoid possible cold code bailouts. + var values = [1, undefined]; + + for (var i = 0; i < 50; ++i) { + deleteLengthIf(arguments, i === 25); + + var expected = values[0 + (i >= 25)]; + assertEq(arguments.length, expected); + } +} +for (var i = 0; i < 20; ++i) testBailoutLengthDeleted(1); diff --git a/js/src/jit-test/tests/warp/bailout-inline-fun-call-no-args.js b/js/src/jit-test/tests/warp/bailout-inline-fun-call-no-args.js new file mode 100644 index 0000000000..be888552e6 --- /dev/null +++ b/js/src/jit-test/tests/warp/bailout-inline-fun-call-no-args.js @@ -0,0 +1,19 @@ +// |jit-test| --fast-warmup; --no-threads + +var iter = 0; + +function foo() { + var x = iter; + bailout(); + return x; +} + +function bar(x) { + return foo.call(); +} + +with ({}) {} +for (var i = 0; i < 100; i++) { + iter = i; + assertEq(bar(), i); +} diff --git a/js/src/jit-test/tests/warp/bailout-inline-getter.js b/js/src/jit-test/tests/warp/bailout-inline-getter.js new file mode 100644 index 0000000000..86ef1300ec --- /dev/null +++ b/js/src/jit-test/tests/warp/bailout-inline-getter.js @@ -0,0 +1,22 @@ +// |jit-test| --fast-warmup; --no-threads + +var iter = 0; + +class A { + get foo() { + var x = iter; + bailout(); + return x; + } +} + +var a = new A(); +function bar() { + return a.foo; +} + +with ({}) {} +for(var i = 0; i < 100; i++) { + iter = i; + assertEq(bar(), i); +} diff --git a/js/src/jit-test/tests/warp/booleantostring.js b/js/src/jit-test/tests/warp/booleantostring.js new file mode 100644 index 0000000000..39ff0b1b6d --- /dev/null +++ b/js/src/jit-test/tests/warp/booleantostring.js @@ -0,0 +1,9 @@ +var a = [true, false]; +for (var i = 0; i < 1e4; i++) { + var str = "x: " + a[i & 1]; + if (i & 1) { + assertEq(str, "x: false"); + } else { + assertEq(str, "x: true"); + } +} diff --git a/js/src/jit-test/tests/warp/bug1646041.js b/js/src/jit-test/tests/warp/bug1646041.js new file mode 100644 index 0000000000..892553ed9f --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1646041.js @@ -0,0 +1,9 @@ +// |jit-test| --ion-warmup-threshold=2 +function f() { + while (true) { + return 1; + } +} +for (var i = 0; i < 100; i++) { + f(); +} diff --git a/js/src/jit-test/tests/warp/bug1646302.js b/js/src/jit-test/tests/warp/bug1646302.js new file mode 100644 index 0000000000..bf86fe702c --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1646302.js @@ -0,0 +1,9 @@ +function f(x) { + function fnc() {} + fnc.prototype = 3; + new fnc; + if (x < 50) { + new new.target(x + 1); + } +} +new f(0); diff --git a/js/src/jit-test/tests/warp/bug1647054.js b/js/src/jit-test/tests/warp/bug1647054.js new file mode 100644 index 0000000000..fa868c60a8 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1647054.js @@ -0,0 +1,8 @@ +function f() { + for (var i = 0; i < 200; ++i) { + for (var j = 0; 0 & ++i; ++j) { + i(); + } + } +} +f(); diff --git a/js/src/jit-test/tests/warp/bug1652049.js b/js/src/jit-test/tests/warp/bug1652049.js new file mode 100644 index 0000000000..a28e35d353 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1652049.js @@ -0,0 +1,7 @@ +function f() { + var o = {__proto__: null}; + for (var i = 0; i < 15; i++) { + assertEq("foo" in o, false); + } +} +f(); diff --git a/js/src/jit-test/tests/warp/bug1652732.js b/js/src/jit-test/tests/warp/bug1652732.js new file mode 100644 index 0000000000..f5df3bc648 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1652732.js @@ -0,0 +1,8 @@ +function test() { + var obj = {}; + obj[{}] = 1; + f = () => { for (var x of obj) {} }; +} +for (var i = 0; i < 5; i++) { + test(); +} diff --git a/js/src/jit-test/tests/warp/bug1653913.js b/js/src/jit-test/tests/warp/bug1653913.js new file mode 100644 index 0000000000..adaaf3c7c8 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1653913.js @@ -0,0 +1,3 @@ +var s = "aaaaaaaaaaaa"; +var a = [, [...s]]; +assertEq(a.toString(), ",a,a,a,a,a,a,a,a,a,a,a,a"); diff --git a/js/src/jit-test/tests/warp/bug1653972.js b/js/src/jit-test/tests/warp/bug1653972.js new file mode 100644 index 0000000000..953bb0077f --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1653972.js @@ -0,0 +1,11 @@ +function maybeSetLength(arr, b) { + if (b) { + arr.length = 0x8000_1111; + } +} +var arr = []; +for (var i = 0; i < 1600; i++) { + maybeSetLength(arr, i > 1500); + arr.push(2); +} +assertEq(arr.length, 0x8000_1112); diff --git a/js/src/jit-test/tests/warp/bug1661530.js b/js/src/jit-test/tests/warp/bug1661530.js new file mode 100644 index 0000000000..c4ea7fdb92 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1661530.js @@ -0,0 +1,7 @@ +Function.prototype.call = function() {}; +var sum = 0; +function foo() { sum++; } +for (var i = 0; i < 1000; i++) { + foo.call({}, 0); +} +assertEq(sum, 0); diff --git a/js/src/jit-test/tests/warp/bug1661728.js b/js/src/jit-test/tests/warp/bug1661728.js new file mode 100644 index 0000000000..ac4ad86552 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1661728.js @@ -0,0 +1,43 @@ +with ({}) {} // Don't inline anything into the top-level script. + +function foo() {} + +function inline_foo_into_bar() { + with ({}) {} // Don't inline anything into this function. + for (var i = 0; i < 10; i++) { + bar(2); + } + +} + +function bar(x) { + switch (x) { + case 1: + inline_foo_into_bar(); + + // Trigger a compacting gc to discard foo's jitscript. + // Do it while bar is on the stack to keep bar's jitscript alive. + gc(foo, 'shrinking'); + break; + case 2: + foo(); + break; + case 3: + break; + } +} + +// Warm up foo and bar. +for (var i = 0; i < 10; i++) { + foo(); + bar(3); +} + +// Inline and discard foo's jitscript. +bar(1); + +// Warp-compile bar +for (var i = 0; i < 50; i++) { + foo(); // ensure that foo has a new jitscript + bar(3); +} diff --git a/js/src/jit-test/tests/warp/bug1662146.js b/js/src/jit-test/tests/warp/bug1662146.js new file mode 100644 index 0000000000..cfc62b98f2 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1662146.js @@ -0,0 +1,12 @@ +var sum = 0; +function f1(a) { + try { + not_a_function(); + } catch (e) { + sum++; + } +} +for (var i = 0; i < 50; ++i) { + f1.call(); +} +assertEq(sum, 50); diff --git a/js/src/jit-test/tests/warp/bug1663993.js b/js/src/jit-test/tests/warp/bug1663993.js new file mode 100644 index 0000000000..c7199e85cf --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1663993.js @@ -0,0 +1,9 @@ +function r(relazify) { + "foo".substr(0); + if (relazify) relazifyFunctions(); +} + +for (var i = 0; i < 10; i++) { + r(i == 9); + r(""); +} diff --git a/js/src/jit-test/tests/warp/bug1664007.js b/js/src/jit-test/tests/warp/bug1664007.js new file mode 100644 index 0000000000..de5d8d1233 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1664007.js @@ -0,0 +1,12 @@ +function f(depth) { + function Obj() { + this.prop = null; + this.prop = this; + } + var o = new Obj(); + assertEq(o.prop, o); + if (depth < 1000) { + f(depth + 1); + } +} +f(0); diff --git a/js/src/jit-test/tests/warp/bug1665303.js b/js/src/jit-test/tests/warp/bug1665303.js new file mode 100644 index 0000000000..b2c26e01cf --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1665303.js @@ -0,0 +1,20 @@ +// |jit-test| skip-if: !('oomTest' in this); --fast-warmup + +// Prevent slowness with --ion-eager. +setJitCompilerOption("ion.warmup.trigger", 100); + +function f() { return 1; } +function test() { + oomTest(function() { + function foo() { + for (var i = 0; i < 10; i++) { + f(); + trialInline(); + } + } + evaluate(foo.toString() + "foo()"); + }); +} +for (var i = 0; i < 3; i++) { + test(); +} diff --git a/js/src/jit-test/tests/warp/bug1666070.js b/js/src/jit-test/tests/warp/bug1666070.js new file mode 100644 index 0000000000..54f737689a --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1666070.js @@ -0,0 +1,8 @@ +// |jit-test| --fast-warmup +function f() {} +for (var i = 0; i < 15; i++) { + f(); + var g = newGlobal(); + g.trialInline(); +} +trialInline(); diff --git a/js/src/jit-test/tests/warp/bug1666142-1.js b/js/src/jit-test/tests/warp/bug1666142-1.js new file mode 100644 index 0000000000..b5bb0aca77 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1666142-1.js @@ -0,0 +1,19 @@ +// |jit-test| --fast-warmup + +// This test triggers a GC in CreateThisForIC, +// without using the arguments rectifier. +var records = []; +function Record() { + return Object.create(null); +} +function init() { + records.push(new Record()); +} +function f() { + for (var i = 0; i < 100; i++) { + init(); + } +} + +gczeal(14,25); +f(); diff --git a/js/src/jit-test/tests/warp/bug1666142-2.js b/js/src/jit-test/tests/warp/bug1666142-2.js new file mode 100644 index 0000000000..9fa94e8f20 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1666142-2.js @@ -0,0 +1,19 @@ +// |jit-test| --fast-warmup + +// This test triggers a GC in CreateThisForIC, +// while using the arguments rectifier. +var records = []; +function Record(val) { + return Object.create(null); +} +function init() { + records.push(new Record()); +} +function f() { + for (var i = 0; i < 100; i++) { + init(); + } +} + +gczeal(14,25); +f(); diff --git a/js/src/jit-test/tests/warp/bug1667680.js b/js/src/jit-test/tests/warp/bug1667680.js new file mode 100644 index 0000000000..8e1b8c0097 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1667680.js @@ -0,0 +1,8 @@ +// |jit-test| --ion-limit-script-size=off +function f() { + var s = "for (var i = 0; i < 100; i++) {}; return 2;"; + s += "var x = [" + "9,".repeat(100_000) + "];"; + var g = Function(s); + assertEq(g(), 2); +} +f(); diff --git a/js/src/jit-test/tests/warp/bug1667685.js b/js/src/jit-test/tests/warp/bug1667685.js new file mode 100644 index 0000000000..2b9e392d24 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1667685.js @@ -0,0 +1,27 @@ +// |jit-test| skip-if: !('oomTest' in this); --fast-warmup + +// Prevent slowness with --ion-eager. +setJitCompilerOption("ion.warmup.trigger", 100); + +function h() { + return 1; +} +function g() { + for (var j = 0; j < 10; j++) { + h(); + } + trialInline(); +} +function f() { + for (var i = 0; i < 2; i++) { + var fun = Function(g.toString() + "g()"); + try { + fun(); + } catch {} + try { + fun(); + } catch {} + } + +} +oomTest(f); diff --git a/js/src/jit-test/tests/warp/bug1667699.js b/js/src/jit-test/tests/warp/bug1667699.js new file mode 100644 index 0000000000..a88115c7c5 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1667699.js @@ -0,0 +1,15 @@ +// |jit-test| --fast-warmup +function f(s) { + // Trial-inline self-hosted |replace| and relazify. + for (var i = 0; i < 50; i++) { + s = s.replace("a", "b"); + } + trialInline(); + relazifyFunctions(); + + // Warp-compile. + for (var j = 0; j < 50; j++) {} + + return s; +} +assertEq(f("a"), "b"); diff --git a/js/src/jit-test/tests/warp/bug1668197.js b/js/src/jit-test/tests/warp/bug1668197.js new file mode 100644 index 0000000000..2dcd6cb376 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1668197.js @@ -0,0 +1,6 @@ +// |jit-test| skip-if: !('oomTest' in this) +function f(x, y) { + return ~Math.hypot(x >>> 0, 2 - x >>> 0); +} +f(2, Math); +oomTest(f); diff --git a/js/src/jit-test/tests/warp/bug1669415.js b/js/src/jit-test/tests/warp/bug1669415.js new file mode 100644 index 0000000000..e22497f86d --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1669415.js @@ -0,0 +1,11 @@ +function f(x, y) { + return +(-y ? -x : (y ? x : NaN)); +} +let arr = [false, {}, {}]; +for (let i = 0; i < 9; ++i) { + f(1.1, 2); +} +for (let i = 0; i < arr.length; i++) { + output = f(true, arr[i]); +} +assertEq(output, 1); diff --git a/js/src/jit-test/tests/warp/bug1669597.js b/js/src/jit-test/tests/warp/bug1669597.js new file mode 100644 index 0000000000..c4110c581f --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1669597.js @@ -0,0 +1,14 @@ +// |jit-test| --fast-warmup +var str = ''; +function g(x) { + with(this) {} // Don't inline. + return x; +} +function f() { + var x = 0; + for (var i = 0; i < 100; i++) { + x += +g(+str); + } + return x; +} +assertEq(f(), 0); diff --git a/js/src/jit-test/tests/warp/bug1671812.js b/js/src/jit-test/tests/warp/bug1671812.js new file mode 100644 index 0000000000..560a01a871 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1671812.js @@ -0,0 +1,11 @@ +// |jit-test| --fast-warmup; --baseline-eager +function f() { + let val1 = Math.sqrt(9007199254740992); + let val2 = 0; + let arr = new Float32Array(100); + for (let i = 0; i < 100; i++) { + val2 = arr[i]; + } + return val1 + val2; +} +assertEq(f(), Math.sqrt(9007199254740992)); diff --git a/js/src/jit-test/tests/warp/bug1676631.js b/js/src/jit-test/tests/warp/bug1676631.js new file mode 100644 index 0000000000..d9f7947396 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1676631.js @@ -0,0 +1,7 @@ +function f() { + var a = arguments; + for (var i = 0; i < 10; i++) { + a[""] + } +} +f(); diff --git a/js/src/jit-test/tests/warp/bug1676639.js b/js/src/jit-test/tests/warp/bug1676639.js new file mode 100644 index 0000000000..f813c53a9a --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1676639.js @@ -0,0 +1,7 @@ +function foo() { + return Math.atanh(true === Math.fround(0) | 0) != true; +} +var results = []; +for (var j = 0; j < 50; j++) { + results.push(foo(0,0)); +} diff --git a/js/src/jit-test/tests/warp/bug1681056.js b/js/src/jit-test/tests/warp/bug1681056.js new file mode 100644 index 0000000000..6a3d094854 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1681056.js @@ -0,0 +1,9 @@ +gczeal(14,10); +let y = []; +try { + evaluate(`(function() { + for (let x10 = 0; + new class Object extends Object { v = function () {} }; + arguments << this) {} + })()`); +} catch(exc) {} diff --git a/js/src/jit-test/tests/warp/bug1681597.js b/js/src/jit-test/tests/warp/bug1681597.js new file mode 100644 index 0000000000..cc0d83f4b9 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1681597.js @@ -0,0 +1,19 @@ +// |jit-test| --fast-warmup; --no-threads + +function f(x) { + (function () { + (1 == (x & 0) * 1.1) + x; + })(); +} +let y = [,,,2147483648,0,0]; +for (let i = 0; i < 6; i++) { + f(y[i]); + f(y[i]); + f(y[i]); + f(y[i]); + f(y[i]); + f(y[i]); + f(y[i]); + f(y[i]); + f(y[i]); +} diff --git a/js/src/jit-test/tests/warp/bug1681677.js b/js/src/jit-test/tests/warp/bug1681677.js new file mode 100644 index 0000000000..157a28740b --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1681677.js @@ -0,0 +1,7 @@ +var a = [1]; +var p = {__proto__: Array.prototype}; +Object.setPrototypeOf(a, p); +for (var i = 0; i < 100; ++i) { + var x = a.slice(0); + assertEq(x.__proto__, Array.prototype); +} diff --git a/js/src/jit-test/tests/warp/bug1681806.js b/js/src/jit-test/tests/warp/bug1681806.js new file mode 100644 index 0000000000..8dfdfd1b4c --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1681806.js @@ -0,0 +1,92 @@ +// |jit-test| skip-if: !getJitCompilerOptions()['ion.enable'] + +with ({}) {} + +let foo, bar, active; + +function test(depth) { + print(depth); + + // Define two mutually recursive functions with as many locals as possible + // to maximize the size of the rematerialized frame when we bail out (~4K). + foo = new Function('n', ` + var a0,b0,c0,d0,e0,f0,g0,h0,i0,j0,k0,l0,m0,n0,o0,p0,q0,r0,s0,t0,u0,v0,w0,x0,y0,z0; + var a1,b1,c1,d1,e1,f1,g1,h1,i1,j1,k1,l1,m1,n1,o1,p1,q1,r1,s1,t1,u1,v1,w1,x1,y1,z1; + var a2,b2,c2,d2,e2,f2,g2,h2,i2,j2,k2,l2,m2,n2,o2,p2,q2,r2,s2,t2,u2,v2,w2,x2,y2,z2; + var a3,b3,c3,d3,e3,f3,g3,h3,i3,j3,k3,l3,m3,n3,o3,p3,q3,r3,s3,t3,u3,v3,w3,x3,y3,z3; + var a4,b4,c4,d4,e4,f4,g4,h4,i4,j4,k4,l4,m4,n4,o4,p4,q4,r4,s4,t4,u4,v4,w4,x4,y4,z4; + var a5,b5,c5,d5,e5,f5,g5,h5,i5,j5,k5,l5,m5,n5,o5,p5,q5,r5,s5,t5,u5,v5,w5,x5,y5,z5; + var a6,b6,c6,d6,e6,f6,g6,h6,i6,j6,k6,l6,m6,n6,o6,p6,q6,r6,s6,t6,u6,v6,w6,x6,y6,z6; + var a7,b7,c7,d7,e7,f7,g7,h7,i7,j7,k7,l7,m7,n7,o7,p7,q7,r7,s7,t7,u7,v7,w7,x7,y7,z7; + var a8,b8,c8,d8,e8,f8,g8,h8,i8,j8,k8,l8,m8,n8,o8,p8,q8,r8,s8,t8,u8,v8,w8,x8,y8,z8; + var a9,b9,c9,d9,e9,f9,g9,h9,i9,j9,k9,l9,m9,n9,o9,p9,q9,r9,s9,t9; + if (n == 0) { + if (active) invalidate(); + } else { + bar(n); + }`); + bar = new Function('n', ` + var a0,b0,c0,d0,e0,f0,g0,h0,i0,j0,k0,l0,m0,n0,o0,p0,q0,r0,s0,t0,u0,v0,w0,x0,y0,z0; + var a1,b1,c1,d1,e1,f1,g1,h1,i1,j1,k1,l1,m1,n1,o1,p1,q1,r1,s1,t1,u1,v1,w1,x1,y1,z1; + var a2,b2,c2,d2,e2,f2,g2,h2,i2,j2,k2,l2,m2,n2,o2,p2,q2,r2,s2,t2,u2,v2,w2,x2,y2,z2; + var a3,b3,c3,d3,e3,f3,g3,h3,i3,j3,k3,l3,m3,n3,o3,p3,q3,r3,s3,t3,u3,v3,w3,x3,y3,z3; + var a4,b4,c4,d4,e4,f4,g4,h4,i4,j4,k4,l4,m4,n4,o4,p4,q4,r4,s4,t4,u4,v4,w4,x4,y4,z4; + var a5,b5,c5,d5,e5,f5,g5,h5,i5,j5,k5,l5,m5,n5,o5,p5,q5,r5,s5,t5,u5,v5,w5,x5,y5,z5; + var a6,b6,c6,d6,e6,f6,g6,h6,i6,j6,k6,l6,m6,n6,o6,p6,q6,r6,s6,t6,u6,v6,w6,x6,y6,z6; + var a7,b7,c7,d7,e7,f7,g7,h7,i7,j7,k7,l7,m7,n7,o7,p7,q7,r7,s7,t7,u7,v7,w7,x7,y7,z7; + var a8,b8,c8,d8,e8,f8,g8,h8,i8,j8,k8,l8,m8,n8,o8,p8,q8,r8,s8,t8,u8,v8,w8,x8,y8,z8; + var a9,b9,c9,d9,e9,f9,g9,h9,i9,j9,k9,l9,m9,n9,o9,p9,q9,r9,s9,t9; + foo(n-1);`); + + with ({}) {} + + // Warm up the invalidate() branch of foo to avoid FirstExecution bailouts. + active = true; + for (var i = 0; i < 10; i++) { + foo(2); + } + + // Warp-compile foo, inlining bar. + active = false; + for (var i = 0; i < 30; i++) { + foo(2); + } + + // Consume stack with frames that don't have to be invalidated. + function recurse(n) { + with ({}) {} + if (n == 0) { + foo(2); + } else { + recurse(n-1); + } + } + + // Trigger an invalidation. + active = true; + recurse(depth); +} + +// Binary search to find the right recursion depth such that +// the invalidation bailout will cause stack overflow. +let depth = 0; +function probeStackLimit(increment) { + try { + while (true) { + test(depth + increment); + depth += increment; + } + } catch {} +} + +probeStackLimit(8192); +probeStackLimit(4096); +probeStackLimit(2048); +probeStackLimit(1024); +probeStackLimit(512); +probeStackLimit(256); +probeStackLimit(128); +probeStackLimit(64); +probeStackLimit(32); +probeStackLimit(16); +probeStackLimit(8); diff --git a/js/src/jit-test/tests/warp/bug1683306.js b/js/src/jit-test/tests/warp/bug1683306.js new file mode 100644 index 0000000000..3c74c3f612 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1683306.js @@ -0,0 +1,19 @@ +// |jit-test| --ion-offthread-compile=off; --ion-full-warmup-threshold=0; --ion-gvn=off; --baseline-eager +// +// Bug 1683306 - Assertion failure: !genObj->hasStackStorage() || genObj->isStackStorageEmpty(), at vm/GeneratorObject.cpp:144 + +function assert(mustBeTrue, message) { } +assert.sameValue = function (expected) { + assert._toString(expected) +}; +assert._toString = function (value) { + return String(value); +} +async function fn() { + for await ([] of []) { } +} + +fn(); +bailAfter(10); +assert.sameValue(); +evaluate("fn();"); diff --git a/js/src/jit-test/tests/warp/bug1683309.js b/js/src/jit-test/tests/warp/bug1683309.js new file mode 100644 index 0000000000..63a0defe4e --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1683309.js @@ -0,0 +1,19 @@ +// |jit-test| slow; --ion-offthread-compile=off; +// +// Bug 1683309: Assertion failure: [barrier verifier] Unmarked edge: JS Object 0xebbb6d1dee0 'object slot' edge to JS Object 0xebbb6d29f60, at gc/Verifier.cpp:392 + + +if (helperThreadCount() > 0) { + evalInWorker(` + try{ + gczeal(4); + function f86(depth) { + var x = async target => ([]); + o62 = unescape; + x(o62.prop, o62); + f86(true + 1); + } + f86(0); + } catch (e) {} + `); +} diff --git a/js/src/jit-test/tests/warp/bug1683535-1.js b/js/src/jit-test/tests/warp/bug1683535-1.js new file mode 100644 index 0000000000..c85d301c3a --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1683535-1.js @@ -0,0 +1,6 @@ +function f(x, y) { + (Math.log() ? 0 : Math.abs(~y)) ^ x ? x : x; +} +for (let i = 0; i < 52; i++) { + f(0, -2147483649); +} diff --git a/js/src/jit-test/tests/warp/bug1683535-2.js b/js/src/jit-test/tests/warp/bug1683535-2.js new file mode 100644 index 0000000000..25317b1d8d --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1683535-2.js @@ -0,0 +1,12 @@ +function foo(y) { + return (1 >>> y >> 12) % 1.5 >>> 0 +} +function bar(y) { + return (1 >>> y >> 12) / 1.5 >>> 0 +} + +with ({}) {} +for (var i = 0; i < 100; i++) { + foo(1); + bar(1); +} diff --git a/js/src/jit-test/tests/warp/bug1683614.js b/js/src/jit-test/tests/warp/bug1683614.js new file mode 100644 index 0000000000..1d0a4724ff --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1683614.js @@ -0,0 +1,13 @@ +// |jit-test| --ion-offthread-compile=off; --ion-full-warmup-threshold=0; --baseline-eager; skip-if: !this.hasOwnProperty("ReadableStream") + +gczeal(9, 8); +function s() { } +new ReadableStream({ + start() { + test(); + } +}); +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/warp/bug1686207.js b/js/src/jit-test/tests/warp/bug1686207.js new file mode 100644 index 0000000000..4a2637408c --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1686207.js @@ -0,0 +1,11 @@ +function f(x, y) { + x >> (y >>> 0) +} + +with ({}) {} + +f(-1, -1) +f(1.5, 0) +for (var i = 0; i < 100; i++) { + f(0, 0); +} diff --git a/js/src/jit-test/tests/warp/bug1686702.js b/js/src/jit-test/tests/warp/bug1686702.js new file mode 100644 index 0000000000..b5bf4e4b54 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1686702.js @@ -0,0 +1,3 @@ +for (var j = 0; j < 100; j++) { + +(Math.fround(1) && 0); +} diff --git a/js/src/jit-test/tests/warp/bug1687661.js b/js/src/jit-test/tests/warp/bug1687661.js new file mode 100644 index 0000000000..e1b90a1879 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1687661.js @@ -0,0 +1,13 @@ +function f(x,y) { + return Math.trunc(+(y ? x : y) || ~y); +} + +with ({}) {} + +for (var i = 0; i < 10; i++) { + f(0,1); + f(NaN,1); + f(0.1,0); +} + +assertEq(f(0.1, 1), 0); diff --git a/js/src/jit-test/tests/warp/bug1687672.js b/js/src/jit-test/tests/warp/bug1687672.js new file mode 100644 index 0000000000..e5d1a746f3 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1687672.js @@ -0,0 +1,14 @@ +// |jit-test| --no-threads; --baseline-warmup-threshold=1; --ion-warmup-threshold=0 + +var input = ["", 0, "", "", "", "", "", "", "", "", "", "", "", "", {}, {}, ""]; + +for (var i = 0; i < 10; i++) { + function sum_indexing(x,i) { + if (i == x.length) { + return 0; + } else { + return x[i] + sum_indexing(x, i+1); + } + } + sum_indexing(input, 0); +} diff --git a/js/src/jit-test/tests/warp/bug1688136.js b/js/src/jit-test/tests/warp/bug1688136.js new file mode 100644 index 0000000000..a7cf52097f --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1688136.js @@ -0,0 +1,13 @@ +function f(a, c) { + if (c) { + a++; + } else { + a--; + } + return (a + a) | 0; +} + +with ({}) {} +for (var i = 0; i < 100; i++) { + f(2147483647, i % 2); +} diff --git a/js/src/jit-test/tests/warp/bug1688346.js b/js/src/jit-test/tests/warp/bug1688346.js new file mode 100644 index 0000000000..0e3bbcdd05 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1688346.js @@ -0,0 +1,12 @@ +function f(x) { + let y = Math.trunc(x); + return y - y; +} + +with ({}) {} + +for (var i = 0; i < 50; i++) { + f(0.1); +} + +assertEq(f(NaN), NaN); diff --git a/js/src/jit-test/tests/warp/bug1692857.js b/js/src/jit-test/tests/warp/bug1692857.js new file mode 100644 index 0000000000..0da271200a --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1692857.js @@ -0,0 +1,14 @@ +var arrayView = new Float32Array(new ArrayBuffer(40)); + +function foo() { + var x = arrayView[0]; + if (!x) { + x = arrayView[NaN]; + } + return x; +} + +with ({}) {} +for (var i = 0; i < 100; i++) { + foo(); +} diff --git a/js/src/jit-test/tests/warp/bug1693062-01.js b/js/src/jit-test/tests/warp/bug1693062-01.js new file mode 100644 index 0000000000..d670360d31 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1693062-01.js @@ -0,0 +1,14 @@ +function foo(trigger) { + var x = Math.fround(1.5); + var a = Math.sqrt(2**53); + if (trigger) { + x = a + 1; + } + return x; +} + +with ({}) {} +for (var i = 0; i < 40; i++) { + foo(false); +} +assertEq(foo(true), Math.sqrt(2**53) + 1); diff --git a/js/src/jit-test/tests/warp/bug1693062-02.js b/js/src/jit-test/tests/warp/bug1693062-02.js new file mode 100644 index 0000000000..473b68773b --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1693062-02.js @@ -0,0 +1,16 @@ +function foo(x) { + var result; + if (x) { + result = Math.fround(~x); + } else { + var temp = Math.sqrt(2**53); + for (var i = 0; i < 1000; i++) {} // Trigger OSR + result = temp + 1; + } + return result; +} + +foo(true); +for (var i = 0; i < 10; i++) { + assertEq(foo(false), Math.sqrt(2**53) + 1); +} diff --git a/js/src/jit-test/tests/warp/bug1694600.js b/js/src/jit-test/tests/warp/bug1694600.js new file mode 100644 index 0000000000..7a0ee06343 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1694600.js @@ -0,0 +1,16 @@ +function foo(fn, y) { + var dummy = y > 0 ? 1 : 2; + fn(); + return y * y; +} + +function nop() {} +function throws() { throw 1; } + +with ({}) {} +for (var i = 0; i < 100; i++) { + foo(nop, 0) + try { + foo(throws, 0x7fffffff) + } catch {} +} diff --git a/js/src/jit-test/tests/warp/bug1696897.js b/js/src/jit-test/tests/warp/bug1696897.js new file mode 100644 index 0000000000..fbdd671573 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1696897.js @@ -0,0 +1,8 @@ +function foo() { + Object.defineProperty(this, "valueOf", ({value: 0, writable: true})); + Object.freeze(this); +} + +for (var i = 0; i < 100; i++) { + foo.call(''); +} diff --git a/js/src/jit-test/tests/warp/bug1697451.js b/js/src/jit-test/tests/warp/bug1697451.js new file mode 100644 index 0000000000..9e8c166c99 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1697451.js @@ -0,0 +1,15 @@ +var dummy; +function foo(a) { + dummy = arguments.length; + return a; +} + +function bar(x) { + var arg = x ? 1 : 2; + assertEq(foo(arg), arg); +} + +with({}) {} +for (var i = 0; i < 50; i++) { + bar(i % 2 == 0); +} diff --git a/js/src/jit-test/tests/warp/bug1697483.js b/js/src/jit-test/tests/warp/bug1697483.js new file mode 100644 index 0000000000..189029ab14 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1697483.js @@ -0,0 +1,46 @@ +function baz() { with({}) {}} + +function foo(a,b,c) { + var x1 = a + b; + var y1 = b + c; + var z1 = a + c; + var x2 = a * b; + var y2 = b * c; + var z2 = a * c; + var x3 = a - b; + var y3 = b - c; + var z3 = a - c; + var x4 = b - a; + var y4 = c - b; + var z4 = c - a; + var x1b = 1 + a + b; + var y1b = 1 + b + c; + var z1b = 1 + a + c; + var x2b = 1 + a * b; + var y2b = 1 + b * c; + var z2b = 1 + a * c; + var x3b = 1 + a - b; + var y3b = 1 + b - c; + var z3b = 1 + a - c; + var x4b = 1 + b - a; + var y4b = 1 + c - b; + var z4b = 1 + c - a; + + var arg = arguments[a]; + + baz(x1, y1, z1, x1b, y1b, z1b, + x2, y2, z2, x2b, y2b, z2b, + x3, y3, z3, x3b, y3b, z3b, + x4, y4, z4, x4b, y4b, z4b); + + return arg; +} + +function bar(a,b,c) { + return foo(a+b,b+c,c+a); +} + +with ({}) {} +for (var i = 0; i < 100; i++) { + bar(0,0,0) +} diff --git a/js/src/jit-test/tests/warp/bug1698126.js b/js/src/jit-test/tests/warp/bug1698126.js new file mode 100644 index 0000000000..d0576c2487 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1698126.js @@ -0,0 +1,11 @@ +function bar() { return arguments[0]; } + +function foo() { + var arr = new Float32Array(10); + return bar(arr[0]); +} + +with ({}) {} +for (var i = 0; i < 100; i++) { + assertEq(foo(), 0); +} diff --git a/js/src/jit-test/tests/warp/bug1698609.js b/js/src/jit-test/tests/warp/bug1698609.js new file mode 100644 index 0000000000..d28297c7c6 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1698609.js @@ -0,0 +1,11 @@ +// |jit-test| skip-if: helperThreadCount() === 0; --code-coverage +// Note: --code-coverage is a hack here to disable lazy parsing. + +var src = "function foo(x) { return /abc/.test(x) }"; +var job = offThreadCompileToStencil(src); +var stencil = finishOffThreadStencil(job); +var re = evalStencil(stencil); + +for (var i = 0; i < 200; i++) { + foo("abc"); +} diff --git a/js/src/jit-test/tests/warp/bug1699056.js b/js/src/jit-test/tests/warp/bug1699056.js new file mode 100644 index 0000000000..cb16a5ffc2 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1699056.js @@ -0,0 +1,12 @@ +var a = ''; +var b = ''; + +function foo() { + a += 'x'; + b = b + a; +} + +with ({}) {} +for (var i = 0; i < 50000; i++) { + try { foo() } catch {} +} diff --git a/js/src/jit-test/tests/warp/bug1700579.js b/js/src/jit-test/tests/warp/bug1700579.js new file mode 100644 index 0000000000..73048c3550 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1700579.js @@ -0,0 +1,28 @@ +// |jit-test| --fast-warmup; --ion-offthread-compile=off + +function foo(y) { + var a = y - 1; + + if (y) {} + + return bar(a); +} + +with ({}) {} +var bar; +function bar1 (x,y) { + "use strict"; + return x | 0; +} + +function bar2(x) { + return x; +} + +bar = bar1; +for (var i = 0; i < 100; i++) { + foo(1); +} + +bar = bar2; +assertEq(foo(-2147483648), -2147483649); diff --git a/js/src/jit-test/tests/warp/bug1700616.js b/js/src/jit-test/tests/warp/bug1700616.js new file mode 100644 index 0000000000..9bdd83c474 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1700616.js @@ -0,0 +1,32 @@ +function dummy() { with ({}) {}} + +function foo() { + var a = 0; + var b = 1; + var c = 2; + var d = 3; + + // This call will run before we enter jitcode and won't have IC + // data, so branch pruning will remove the path from the entry + // block to the OSR preheader. + dummy(); + + // We will OSR in this loop. Because there's no path from the + // function entry to the loop, the only information we have + // about a, b, c, and d is that they come from the OSR block. + for (var i = 0; i < 1000; i++) { + + // Create a bunch of phis that only depend on OsrValues. + // These phis will be specialized to MIRType::Value. + a = i % 2 ? b : c; + b = i % 3 ? c : d; + c = i % 4 ? d : a; + d = i % 5 ? a : b; + + // This phi will be optimistically specialized to + // MIRType::String and trigger a bailout. + dummy(i % 6 ? d : ""); + } + return a; +} +foo(); diff --git a/js/src/jit-test/tests/warp/bug1701208.js b/js/src/jit-test/tests/warp/bug1701208.js new file mode 100644 index 0000000000..184c8dbf24 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1701208.js @@ -0,0 +1,18 @@ +// |jit-test| --fast-warmup; --no-threads + +function dummy() { with ({}) {} } + +function foo() { + dummy(); + var x = []; + var y = []; + for (var i = 0; i < 10; i++) { } + for (var i = 0; i < 100; i++) { + var swap = x; + x = y; + y = swap; + } + return x; +} + +foo(); diff --git a/js/src/jit-test/tests/warp/bug1702465.js b/js/src/jit-test/tests/warp/bug1702465.js new file mode 100644 index 0000000000..5143b52ab7 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1702465.js @@ -0,0 +1,14 @@ +function foo() { + if (arguments[0]) { + return arguments[1]; + } +} + +with ({}) {} +for (var i = 0; i < 100; i++) { + foo(true, 1); +} + +for (var i = 0; i < 100; i++) { + foo(false); +} diff --git a/js/src/jit-test/tests/warp/bug1703766.js b/js/src/jit-test/tests/warp/bug1703766.js new file mode 100644 index 0000000000..7c1f420e9b --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1703766.js @@ -0,0 +1,212 @@ +function bar() { return 0;} + +function foo(x) { + switch (x) { + case 0: bar(); break; + case 1: bar(); break; + case 2: bar(); break; + case 3: bar(); break; + case 4: bar(); break; + case 5: bar(); break; + case 6: bar(); break; + case 7: bar(); break; + case 8: bar(); break; + case 9: bar(); break; + case 10: bar(); break; + case 11: bar(); break; + case 12: bar(); break; + case 13: bar(); break; + case 14: bar(); break; + case 15: bar(); break; + case 16: bar(); break; + case 17: bar(); break; + case 18: bar(); break; + case 19: bar(); break; + case 20: bar(); break; + case 21: bar(); break; + case 22: bar(); break; + case 23: bar(); break; + case 24: bar(); break; + case 25: bar(); break; + case 26: bar(); break; + case 27: bar(); break; + case 28: bar(); break; + case 29: bar(); break; + case 30: bar(); break; + case 31: bar(); break; + case 32: bar(); break; + case 33: bar(); break; + case 34: bar(); break; + case 35: bar(); break; + case 36: bar(); break; + case 37: bar(); break; + case 38: bar(); break; + case 39: bar(); break; + case 40: bar(); break; + case 41: bar(); break; + case 42: bar(); break; + case 43: bar(); break; + case 44: bar(); break; + case 45: bar(); break; + case 46: bar(); break; + case 47: bar(); break; + case 48: bar(); break; + case 49: bar(); break; + case 50: bar(); break; + case 51: bar(); break; + case 52: bar(); break; + case 53: bar(); break; + case 54: bar(); break; + case 55: bar(); break; + case 56: bar(); break; + case 57: bar(); break; + case 58: bar(); break; + case 59: bar(); break; + case 60: bar(); break; + case 61: bar(); break; + case 62: bar(); break; + case 63: bar(); break; + case 64: bar(); break; + case 65: bar(); break; + case 66: bar(); break; + case 67: bar(); break; + case 68: bar(); break; + case 69: bar(); break; + case 70: bar(); break; + case 71: bar(); break; + case 72: bar(); break; + case 73: bar(); break; + case 74: bar(); break; + case 75: bar(); break; + case 76: bar(); break; + case 77: bar(); break; + case 78: bar(); break; + case 79: bar(); break; + case 80: bar(); break; + case 81: bar(); break; + case 82: bar(); break; + case 83: bar(); break; + case 84: bar(); break; + case 85: bar(); break; + case 86: bar(); break; + case 87: bar(); break; + case 88: bar(); break; + case 89: bar(); break; + case 90: bar(); break; + case 91: bar(); break; + case 92: bar(); break; + case 93: bar(); break; + case 94: bar(); break; + case 95: bar(); break; + case 96: bar(); break; + case 97: bar(); break; + case 98: bar(); break; + case 99: bar(); break; + case 100: bar(); break; + case 101: bar(); break; + case 102: bar(); break; + case 103: bar(); break; + case 104: bar(); break; + case 105: bar(); break; + case 106: bar(); break; + case 107: bar(); break; + case 108: bar(); break; + case 109: bar(); break; + case 110: bar(); break; + case 111: bar(); break; + case 112: bar(); break; + case 113: bar(); break; + case 114: bar(); break; + case 115: bar(); break; + case 116: bar(); break; + case 117: bar(); break; + case 118: bar(); break; + case 119: bar(); break; + case 120: bar(); break; + case 121: bar(); break; + case 122: bar(); break; + case 123: bar(); break; + case 124: bar(); break; + case 125: bar(); break; + case 126: bar(); break; + case 127: bar(); break; + case 128: bar(); break; + case 129: bar(); break; + case 130: bar(); break; + case 131: bar(); break; + case 132: bar(); break; + case 133: bar(); break; + case 134: bar(); break; + case 135: bar(); break; + case 136: bar(); break; + case 137: bar(); break; + case 138: bar(); break; + case 139: bar(); break; + case 140: bar(); break; + case 141: bar(); break; + case 142: bar(); break; + case 143: bar(); break; + case 144: bar(); break; + case 145: bar(); break; + case 146: bar(); break; + case 147: bar(); break; + case 148: bar(); break; + case 149: bar(); break; + case 150: bar(); break; + case 151: bar(); break; + case 152: bar(); break; + case 153: bar(); break; + case 154: bar(); break; + case 155: bar(); break; + case 156: bar(); break; + case 157: bar(); break; + case 158: bar(); break; + case 159: bar(); break; + case 160: bar(); break; + case 161: bar(); break; + case 162: bar(); break; + case 163: bar(); break; + case 164: bar(); break; + case 165: bar(); break; + case 166: bar(); break; + case 167: bar(); break; + case 168: bar(); break; + case 169: bar(); break; + case 170: bar(); break; + case 171: bar(); break; + case 172: bar(); break; + case 173: bar(); break; + case 174: bar(); break; + case 175: bar(); break; + case 176: bar(); break; + case 177: bar(); break; + case 178: bar(); break; + case 179: bar(); break; + case 180: bar(); break; + case 181: bar(); break; + case 182: bar(); break; + case 183: bar(); break; + case 184: bar(); break; + case 185: bar(); break; + case 186: bar(); break; + case 187: bar(); break; + case 188: bar(); break; + case 189: bar(); break; + case 190: bar(); break; + case 191: bar(); break; + case 192: bar(); break; + case 193: bar(); break; + case 194: bar(); break; + case 195: bar(); break; + case 196: bar(); break; + case 197: bar(); break; + case 198: bar(); break; + case 199: bar(); break; + case 200: bar(); break; + } + return x; +} + +for (var i = 0; i < 5000; i++) { + foo(1) +} diff --git a/js/src/jit-test/tests/warp/bug1703817.js b/js/src/jit-test/tests/warp/bug1703817.js new file mode 100644 index 0000000000..d495e06e5b --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1703817.js @@ -0,0 +1,9 @@ +function foo() { + new Float64Array(0x40000001)() +} + +for (var i = 0; i < 100; i++) { + try { + foo(); + } catch {} +} diff --git a/js/src/jit-test/tests/warp/bug1704467.js b/js/src/jit-test/tests/warp/bug1704467.js new file mode 100644 index 0000000000..6d9172a2ef --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1704467.js @@ -0,0 +1,9 @@ +var always_true = true; +function f() { + var obj = {x: 1}; + for (var i = 0; i < 100; i++) { + var res = always_true ? obj : null; + assertEq(res, obj); + } +} +f(); diff --git a/js/src/jit-test/tests/warp/bug1706314.js b/js/src/jit-test/tests/warp/bug1706314.js new file mode 100644 index 0000000000..210f827a2a --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1706314.js @@ -0,0 +1,9 @@ +function f(a,b) { + var o1 = {x: 1}; + var o2 = {x: 1}; + for (var i = 0; i < 100; i++) { + var res = a ? (b ? o1 : o2) : null; + assertEq(res, o1); + } +} +f(true, true); diff --git a/js/src/jit-test/tests/warp/bug1708839.js b/js/src/jit-test/tests/warp/bug1708839.js new file mode 100644 index 0000000000..93d5c5622e --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1708839.js @@ -0,0 +1,16 @@ +function f(a) { + let m = new Uint32Array([-1]); + let h = m[0]; + let r = m[0]; + if (a) { + h = undefined; + r = 0xff; + } + return h > r; +}; + +assertEq(f(false), false); +for (let i = 0; i < 100; ++i) { + f(true); +} +assertEq(f(false), false); diff --git a/js/src/jit-test/tests/warp/bug1713579.js b/js/src/jit-test/tests/warp/bug1713579.js new file mode 100644 index 0000000000..d6bb30573c --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1713579.js @@ -0,0 +1,8 @@ +function f() { + var i = 0; + while (i < (this.foo = this.foo ^ 123)) { + this.prop = 1; + } +} +new f(); +f(); diff --git a/js/src/jit-test/tests/warp/bug1716231.js b/js/src/jit-test/tests/warp/bug1716231.js new file mode 100644 index 0000000000..df51c0b08f --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1716231.js @@ -0,0 +1,25 @@ +// |jit-test| --fast-warmup; --ion-offthread-compile=off + +const too_big_for_float32 = 67109020; + +function call_with_no_ic_data() {} + +function foo() { + call_with_no_ic_data(); + + let x = too_big_for_float32; + let result; + + // We OSR in this loop. + for (let i = 0; i < 100; i++) { + const float32 = Math.fround(0); + + // Create a phi with one float32-typed input + // and one OSRValue input. + result = float32 || x; + } + + return result; +} + +assertEq(foo(), too_big_for_float32); diff --git a/js/src/jit-test/tests/warp/bug1716931.js b/js/src/jit-test/tests/warp/bug1716931.js new file mode 100644 index 0000000000..ae430f53ff --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1716931.js @@ -0,0 +1,13 @@ +// |jit-test| --fast-warmup; --no-threads + +function* foo(x) { + yield* x; + assertEq(true, false); // Unreachable +} + +for (var i = 0; i < 10; i++) { + var count = 0; + for (var o of foo(Array(50))) { + if (count++ > 40) break; + } +}
\ No newline at end of file diff --git a/js/src/jit-test/tests/warp/bug1719884.js b/js/src/jit-test/tests/warp/bug1719884.js new file mode 100644 index 0000000000..cfcdbd8a2e --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1719884.js @@ -0,0 +1,19 @@ +// |jit-test| --fast-warmup; --no-threads + +var always_true = true; +var unused = 3; + +function foo(input, trigger) { + if (trigger) { + return Math.atan2(always_true ? Math.trunc(input ** -0x80000001) + : unused, + +input); + } +} + +with ({}) {} +for (var i = 0; i < 100; i++) { + foo(1, i == 15); +} + +assertEq(foo(-Infinity, true), -Math.PI); diff --git a/js/src/jit-test/tests/warp/bug1720093-1.js b/js/src/jit-test/tests/warp/bug1720093-1.js new file mode 100644 index 0000000000..8f2286c117 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1720093-1.js @@ -0,0 +1,17 @@ +function main() { + let obj = {2294967295: 0, 2294967296: 1}; + let x; + + for (let i = 0; i < 110; i++) { + let c = 2294967296; + x = --c; + Math.fround(0); + } + + try { + throw 1; + } catch { + assertEq(obj[x], 0); + } +} +main(); diff --git a/js/src/jit-test/tests/warp/bug1720093-2.js b/js/src/jit-test/tests/warp/bug1720093-2.js new file mode 100644 index 0000000000..af66f6477c --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1720093-2.js @@ -0,0 +1,19 @@ +function throws() { with ({}) {} throw 1; } + +function main() { + let obj = {2294967295: 0, 2294967296: 1}; + let x; + + for (let i = 0; i < 110; i++) { + let c = 2294967296; + x = --c; + Math.fround(0); + } + + try { + return throws() + } catch { + assertEq(obj[x], 0); + } +} +main(); diff --git a/js/src/jit-test/tests/warp/bug1732601.js b/js/src/jit-test/tests/warp/bug1732601.js new file mode 100644 index 0000000000..af4d995cb2 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1732601.js @@ -0,0 +1,27 @@ +// |jit-test| --fast-warmup + +function Mixin(Target) { + var c = class extends Target {}; + Target.prototype.x = 1; // Add shadowing property to disable teleporting. + return c; +} +function MixinFoo(Target) { + var c = class extends Target { + get foo() { return 2; } + set foo(value) {} + }; + Target.prototype.x = 1; // Add shadowing property to disable teleporting. + return c; +} + +class Base {} +class MyClass extends Mixin(Mixin(Mixin(Mixin(Mixin(Mixin(Mixin(Mixin(Mixin(Mixin(Mixin(MixinFoo(Base)))))))))))) {} + +function test() { + var instance = new MyClass(); + assertEq(instance.x, 1); + for (var i = 0; i < 500; i++) { + assertEq(instance.foo, 2); + } +} +test(); diff --git a/js/src/jit-test/tests/warp/bug1735157.js b/js/src/jit-test/tests/warp/bug1735157.js new file mode 100644 index 0000000000..aedbe03f98 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1735157.js @@ -0,0 +1,5 @@ +const options = ["", {}]; +let value = ""; +for (let i = 0; i < 10000; i++) { + value += options[(Math.random() < 0.01) | 0]; +} diff --git a/js/src/jit-test/tests/warp/bug1738676.js b/js/src/jit-test/tests/warp/bug1738676.js new file mode 100644 index 0000000000..0d9e8fa568 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1738676.js @@ -0,0 +1,19 @@ +function verify(n) { + with ({}) {} + assertEq(n, -0); +} + +function main() { + let x; + let arr = []; + + let i = 0; + do { + x = (256 - i) * 0; + arr[x] = 0; + i++ + } while (i < 512); + verify(x); +} + +main(); diff --git a/js/src/jit-test/tests/warp/bug1741635-1.js b/js/src/jit-test/tests/warp/bug1741635-1.js new file mode 100644 index 0000000000..bf966da433 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1741635-1.js @@ -0,0 +1,11 @@ +function foo() {} + +var i; +function c(x, ...rest) { + for (i = 0; i < rest.length; i++) + foo(rest[i]) +} + +for (var j = 0; j < 100; j++) { + c(0, 0); +} diff --git a/js/src/jit-test/tests/warp/bug1741635-2.js b/js/src/jit-test/tests/warp/bug1741635-2.js new file mode 100644 index 0000000000..e16d77a139 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1741635-2.js @@ -0,0 +1,11 @@ +// |jit-test| --ion-range-analysis=off + +function f(a, ...rest) { + return rest.length; +} + +with ({}); + +for (let i = 0; i < 1000; ++i) { + assertEq(f(), 0); +} diff --git a/js/src/jit-test/tests/warp/bug1745949.js b/js/src/jit-test/tests/warp/bug1745949.js new file mode 100644 index 0000000000..0047afe7c5 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1745949.js @@ -0,0 +1,11 @@ +function foo() { + let v; + let i = -2000; + do { + v = i * -1; + v += v; + i++; + } while (i < 1); + return Object.is(v, -0); +} +assertEq(foo(), true); diff --git a/js/src/jit-test/tests/warp/bug1761947.js b/js/src/jit-test/tests/warp/bug1761947.js new file mode 100644 index 0000000000..64d6406172 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1761947.js @@ -0,0 +1,28 @@ +let trigger = false; + +function bar(x) { + with ({}) {} + if (trigger) { + gc(foo, "shrinking"); + trigger = false; + } + return Object(x); +} + +function foo() { + let result = undefined; + const arr = [8]; + for (var i = 0; i < 10; i++) { + result = bar(...arr); + assertEq(Number(result), 8); + } + return result; +} + +with ({}) {} +for (var i = 0; i < 100; i++) { + foo(); +} + +trigger = true; +foo(); diff --git a/js/src/jit-test/tests/warp/bug1762769.js b/js/src/jit-test/tests/warp/bug1762769.js new file mode 100644 index 0000000000..2fad563b08 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1762769.js @@ -0,0 +1,8 @@ +try { + try {} + finally { + throw 1; + } +} catch {} + +for (var i = 0; i < 1000; i++) {} diff --git a/js/src/jit-test/tests/warp/bug1762770.js b/js/src/jit-test/tests/warp/bug1762770.js new file mode 100644 index 0000000000..8f693b77e6 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1762770.js @@ -0,0 +1,13 @@ +function* a() { + try { + yield 1; + } finally { + for (c = 0; c < 100; c++); + } +} + +var b = a(); +b.next(); +let result = b.return(42); +assertEq(result.value, 42); +assertEq(result.done, true); diff --git a/js/src/jit-test/tests/warp/bug1763012-1.js b/js/src/jit-test/tests/warp/bug1763012-1.js new file mode 100644 index 0000000000..fc1ce2b5d6 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1763012-1.js @@ -0,0 +1,19 @@ +// |jit-test| --fast-warmup; --no-threads + +function f() { + let counter = 0; + for (var i = 0; i < 8; i++) { + let x = i / i; + for (var j = 0; j < 6; j++) { + if (x === x) { + if (j > 6) {} else {} + } else { + counter += 1; + } + } + } + return counter; +} + +f(); +assertEq(f(), 6); diff --git a/js/src/jit-test/tests/warp/bug1763012-2.js b/js/src/jit-test/tests/warp/bug1763012-2.js new file mode 100644 index 0000000000..4802e2eef2 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1763012-2.js @@ -0,0 +1,19 @@ +// |jit-test| --fast-warmup; --no-threads + +function f() { + let counter = 0; + for (var i = 0; i < 8; i++) { + let x = 0 % i; + for (var j = 0; j < 6; j++) { + if (x === x) { + if (j > 6) {} else {} + } else { + counter += 1; + } + } + } + return counter; +} + +f(); +assertEq(f(), 6); diff --git a/js/src/jit-test/tests/warp/bug1767196.js b/js/src/jit-test/tests/warp/bug1767196.js new file mode 100644 index 0000000000..03b530518e --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1767196.js @@ -0,0 +1,15 @@ +// |jit-test| --fast-warmup; --no-threads +function foo() {} +function main() { + let value = 0.1; + let result; + foo(); + + const neverTrue = false; + for (let i = 0; i < 100; i++) { + let f32 = Math.fround(i) && 0; + result = neverTrue ? f32 : value; + } + return result; +} +assertEq(main(), 0.1); diff --git a/js/src/jit-test/tests/warp/bug1769410.js b/js/src/jit-test/tests/warp/bug1769410.js new file mode 100644 index 0000000000..9d66d96e39 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1769410.js @@ -0,0 +1,14 @@ +// |jit-test| --fast-warmup +function f(x) { + var a = Math.fround(Math.fround(false)); + var b = Math.min(a, x ? Math.fround(x) : Math.fround(x)); + return b >>> 0; +} +function test() { + with (this) {} // Don't inline. + for (var i = 0; i < 100; i++) { + assertEq(f(Infinity), 0); + } + assertEq(f(-1), 4294967295); +} +test(); diff --git a/js/src/jit-test/tests/warp/bug1770904.js b/js/src/jit-test/tests/warp/bug1770904.js new file mode 100644 index 0000000000..c664edfaa4 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1770904.js @@ -0,0 +1,7 @@ +var N = 100; +var f = Function("return 0" + "+arguments[0]".repeat(N)); + +for (let i = 0; i < 10000; ++i) { + assertEq(f(1), N); +} + diff --git a/js/src/jit-test/tests/warp/bug1789821.js b/js/src/jit-test/tests/warp/bug1789821.js new file mode 100644 index 0000000000..b1d3ee9a54 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1789821.js @@ -0,0 +1,14 @@ +function foo(x) { + Math.max(...[x]); +} + +with ({}) {} +for (let i = 0; i < 100; i++) { + foo(0); +} + +let called = false; +const evil = { valueOf: () => { called = true; } }; +foo(evil); + +assertEq(called, true); diff --git a/js/src/jit-test/tests/warp/bug1825220.js b/js/src/jit-test/tests/warp/bug1825220.js new file mode 100644 index 0000000000..11da1ef449 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1825220.js @@ -0,0 +1,11 @@ +// |jit-test| --ion-pruning=off; --fast-warmup + +function foo() { + Date.prototype.toLocaleString() +} + +for (var i = 0; i < 100; i++) { + try { + foo(); + } catch {} +} diff --git a/js/src/jit-test/tests/warp/bug1825408.js b/js/src/jit-test/tests/warp/bug1825408.js new file mode 100644 index 0000000000..1f25817c29 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1825408.js @@ -0,0 +1,20 @@ +// |jit-test| --no-threads; --fast-warmup; --ion-warmup-threshold=0 + +function a(b = (b, !b)) { + () => b +} +for (var i = 0; i < 20; i++) { + a(""); +} + +try { + a(); +} catch {} + +function f(arr) { + for (var i = 0; i < 10; i++) { + a(arr.x); + } +} +f({y: 1, x:1}); +f({x:2}); diff --git a/js/src/jit-test/tests/warp/bug1852238.js b/js/src/jit-test/tests/warp/bug1852238.js new file mode 100644 index 0000000000..eaf8eb8a95 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1852238.js @@ -0,0 +1,14 @@ +// |jit-test| --fast-warmup +(function() { + eval(""); + (function() { + var arr = []; + var res = 0; + for (var i = 2; i < 50; i++) { + for (var j = 1; j < 10; j++) { + res = JSON; + } + } + return res; + })(); +})(); diff --git a/js/src/jit-test/tests/warp/bug1852398.js b/js/src/jit-test/tests/warp/bug1852398.js new file mode 100644 index 0000000000..6e12d2a31d --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1852398.js @@ -0,0 +1,27 @@ +// |jit-test| --no-threads + +function inner(obj, f) { + return obj.x + f(); +} + +function middle(obj, f) { + return inner(obj, f); +} + +function outer(obj, f) { + return middle(obj, f); +} + +var fs = [() => 1, () => 2]; + +with ({}) {} +for (var i = 0; i < 1500; i++) { + var obj = {x: 1}; + obj["y" + i % 2] = 2; + outer(obj, fs[i % 2]); +} +for (var i = 0; i < 1500; i++) { + var obj = {x: 1}; + obj["y" + (3 + (i % 10))] = 2; + outer(obj, fs[i % 2]); +} diff --git a/js/src/jit-test/tests/warp/bug1852702.js b/js/src/jit-test/tests/warp/bug1852702.js new file mode 100644 index 0000000000..c0a5c7f3ca --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1852702.js @@ -0,0 +1,17 @@ +// |jit-test| --fast-warmup +function main() { + var f = function(x) { + x.f = f; + var f2 = x.f; + f2[Symbol.toPrimitive] = null; + try { + new f2(0); + } catch (e) {} + for (var i = 0; i < 100; i++) { } + }; + new f(f); +} +gczeal(2); +for (var i = 0; i < 100; i++) { + main(); +} diff --git a/js/src/jit-test/tests/warp/cancel-offthread-compile.js b/js/src/jit-test/tests/warp/cancel-offthread-compile.js new file mode 100644 index 0000000000..939d128ae1 --- /dev/null +++ b/js/src/jit-test/tests/warp/cancel-offthread-compile.js @@ -0,0 +1,21 @@ +// |jit-test| --fast-warmup; skip-if: helperThreadCount() === 0 + +function foo(o) { + return o.y; +} + +with ({}) {} + +var sum = 0; + +// Trigger an off-thread Warp compile. +for (var i = 0; i < 30; i++) { + sum += foo({y: 1}); +} + +// Attach a new stub and cancel that compile. +for (var i = 0; i < 30; i++) { + sum += foo({x: 1, y: 1}); +} + +assertEq(sum, 60); diff --git a/js/src/jit-test/tests/warp/catch-overflow-regexp.js b/js/src/jit-test/tests/warp/catch-overflow-regexp.js new file mode 100644 index 0000000000..a316e5636b --- /dev/null +++ b/js/src/jit-test/tests/warp/catch-overflow-regexp.js @@ -0,0 +1,8 @@ +function test() { + try { + test(); + } catch { + /a/.test("a"); + } +} +test(); diff --git a/js/src/jit-test/tests/warp/compare-constant-string.js b/js/src/jit-test/tests/warp/compare-constant-string.js new file mode 100644 index 0000000000..d7512c3171 --- /dev/null +++ b/js/src/jit-test/tests/warp/compare-constant-string.js @@ -0,0 +1,90 @@ +// Test case to cover constant string comparison. +// +// Equality comparison for short (≤32 characters), Latin-1 constant strings is +// optimised during lowering. + +function* characters(...ranges) { + for (let [start, end] of ranges) { + for (let i = start; i <= end; ++i) { + yield i; + } + } +} + +const ascii = [...characters( + [0x41, 0x5A], // A..Z + [0x61, 0x7A], // a..z + [0x30, 0x39], // 0..9 +)]; + +const latin1 = [...characters( + [0xC0, 0xFF], // À..ÿ +)]; + +const twoByte = [...characters( + [0x100, 0x17E], // Ā..ž +)]; + +function toRope(s) { + try { + return newRope(s[0], s.substring(1)); + } catch {} + // newRope can fail when |s| fits into an inline string. In that case simply + // return the input. + return s; +} + +function atomize(s) { + return Object.keys({[s]: 0})[0]; +} + +const operators = [ + "==", "===", "!=", "!==", +]; + +for (let i = 1; i <= 32; ++i) { + let strings = [ascii, latin1, twoByte].flatMap(codePoints => [ + // Same string as the input. + String.fromCodePoint(...codePoints.slice(0, i)), + + // Same length as the input, but a different string. + String.fromCodePoint(...codePoints.slice(1, i + 1)), + + // Shorter string than the input. + String.fromCodePoint(...codePoints.slice(0, i - 1)), + + // Longer string than the input. + String.fromCodePoint(...codePoints.slice(0, i + 1)), + ]).flatMap(x => [ + x, + toRope(x), + newString(x, {twoByte: true}), + atomize(x), + ]); + + for (let codePoints of [ascii, latin1, twoByte]) { + let str = String.fromCodePoint(...codePoints.slice(0, i)); + + for (let op of operators) { + let fn = Function("strings", ` + const expected = strings.map(x => { + // Prevent Warp compilation when computing the expected results. + with ({}) ; + return x ${op} "${str}"; + }); + + for (let i = 0; i < 250; ++i) { + let idx = i % strings.length; + let str = strings[idx]; + + let lhs = str ${op} "${str}"; + if (lhs !== expected[idx]) throw new Error(); + + let rhs = "${str}" ${op} str; + if (rhs !== expected[idx]) throw new Error(); + } + `); + fn(strings); + } + } +} diff --git a/js/src/jit-test/tests/warp/compare-empty-string.js b/js/src/jit-test/tests/warp/compare-empty-string.js new file mode 100644 index 0000000000..30cf03f874 --- /dev/null +++ b/js/src/jit-test/tests/warp/compare-empty-string.js @@ -0,0 +1,50 @@ +// Test case to cover empty string comparison folding. +// +// MCompare can fold comparison with an empty string constant and replace it +// with |string.length <op> 0|. + +const strings = [ + // Zero length string. + "", + + // Uncommon zero length strings. + newString("", {external: true}), + + // Latin-1 strings. + "a", + "ä", + "monkey", + + // Two-byte strings. + "猿", + "🐒", + newString("monkey", {twoByte: true}), +]; + +const operators = [ + "==", "===", "!=", "!==", + "<", "<=", ">=", ">", +]; + +for (let op of operators) { + let lhs = x => `${x} ${op} ""`; + let rhs = x => `"" ${op} ${x}`; + + for (let input of [lhs, rhs]) { + let fn = Function("strings", ` + const expected = strings.map(x => { + // Prevent Warp compilation when computing the expected results. + with ({}) ; + return ${input("x")}; + }); + + for (let i = 0; i < 200; ++i) { + let idx = i % strings.length; + let str = strings[idx]; + let res = ${input("str")}; + assertEq(res, expected[idx]); + } + `); + fn(strings); + } +} diff --git a/js/src/jit-test/tests/warp/conditional-test-guard.js b/js/src/jit-test/tests/warp/conditional-test-guard.js new file mode 100644 index 0000000000..cc2cca8660 --- /dev/null +++ b/js/src/jit-test/tests/warp/conditional-test-guard.js @@ -0,0 +1,12 @@ +function f(x) { + return (+(x || 1) ? 0 : x); +} + +// Prevent top-level Ion-compilation, so we don't inline |f|. +with ({}); + +for (let i = 0; i < 100; ++i) { + assertEq(f(), 0); +} + +assertEq(f("not-a-number"), "not-a-number"); diff --git a/js/src/jit-test/tests/warp/conditional-test-undefined-1.js b/js/src/jit-test/tests/warp/conditional-test-undefined-1.js new file mode 100644 index 0000000000..382f3ca966 --- /dev/null +++ b/js/src/jit-test/tests/warp/conditional-test-undefined-1.js @@ -0,0 +1,22 @@ +function g(array) { + // 1. Absent properties return |undefined| from CacheIR. + // 2. Tests on |undefined| are changed to |false| in CacheIR. + // + // When Warp compiling the CacheIR ops, the first test will then happen on + // a boolean, whereas the phi still sees the original undefined value. + if (array.does_not_exist || array.slice) { + return 1; + } + return 0; +} + +function f() { + var array = []; + var r = 0; + for (let i = 0; i < 100; ++i) { + r += g(array); + } + assertEq(r, 100); +} + +for (let i = 0; i < 2; ++i) f();
\ No newline at end of file diff --git a/js/src/jit-test/tests/warp/conditional-test-undefined-2.js b/js/src/jit-test/tests/warp/conditional-test-undefined-2.js new file mode 100644 index 0000000000..b0dada6684 --- /dev/null +++ b/js/src/jit-test/tests/warp/conditional-test-undefined-2.js @@ -0,0 +1,22 @@ +function g(array) { + // 1. Absent properties return |undefined| from CacheIR. + // 2. Tests on |undefined| are changed to |false| in CacheIR. + // + // When Warp compiling the CacheIR ops, the first test will then happen on + // a boolean, whereas the phi still sees the original undefined value. + if (array.does_not_exist || array.does_not_exist_too || array.slice) { + return 1; + } + return 0; +} + +function f() { + var array = []; + var r = 0; + for (let i = 0; i < 100; ++i) { + r += g(array); + } + assertEq(r, 100); +} + +for (let i = 0; i < 2; ++i) f();
\ No newline at end of file diff --git a/js/src/jit-test/tests/warp/force-warp.js b/js/src/jit-test/tests/warp/force-warp.js new file mode 100644 index 0000000000..b875fc0471 --- /dev/null +++ b/js/src/jit-test/tests/warp/force-warp.js @@ -0,0 +1,11 @@ +// A simple test to ensure WarpBuilder files are included in code-coverage builds. +// See bug 1635097. + +function test() { + var o = {x: 0}; + for (var i = 0; i < 10000; i++) { + o.x++; + } + return o; +} +test(); diff --git a/js/src/jit-test/tests/warp/fun-call-not-inlined.js b/js/src/jit-test/tests/warp/fun-call-not-inlined.js new file mode 100644 index 0000000000..3428167de5 --- /dev/null +++ b/js/src/jit-test/tests/warp/fun-call-not-inlined.js @@ -0,0 +1,29 @@ +// |jit-test| --fast-warmup + +// Call a scripted function instead of Function.prototype.call. +function testScriptedAtFunCallOp() { + var f = function(x) { + if (x === 130) bailout(); + return x; + }; + f.call = f; + + for (var i = 0; i < 150; i++) { + assertEq(f.call(i), i); + } +} +testScriptedAtFunCallOp(); + +// Call Function.prototype.call instead of a "normal" function. +function testFunCallAtNormalCallOp() { + var f = function(x) { + if (x === 130) bailout(); + return x; + }; + f.myCall = Function.prototype.call; + + for (var i = 0; i < 150; i++) { + assertEq(f.myCall(null, i), i); + } +} +testFunCallAtNormalCallOp(); diff --git a/js/src/jit-test/tests/warp/function-load-length.js b/js/src/jit-test/tests/warp/function-load-length.js new file mode 100644 index 0000000000..c3ef3552eb --- /dev/null +++ b/js/src/jit-test/tests/warp/function-load-length.js @@ -0,0 +1,86 @@ +// Test transpiling of LoadFunctionLengthResult and cover possible bailout conditions. + +function empty() {} + +// Note: Typically won't use LoadFunctionLengthResult, because the "length" +// property will be resolved on the first access. +function testGlobalFunction() { + for (var i = 0; i < 200; ++i) { + assertEq(empty.length, 0); + } +} +testGlobalFunction(); + +// Note: Typically won't use LoadFunctionLengthResult, because the "length" +// property will be resolved on the first access. +function testInnerFunction() { + function f() {} + for (var i = 0; i < 200; ++i) { + assertEq(f.length, 0); + } +} +testInnerFunction(); + +function testPerLoopFunction() { + for (var i = 0; i < 200; ++i) { + assertEq(function(){}.length, 0); + } +} +testPerLoopFunction(); + +// Note: Typically won't use LoadFunctionLengthResult, because the "length" +// property will be resolved on the first access. +function testNativeFunction() { + for (var i = 0; i < 200; ++i) { + assertEq(Math.max.length, 2); + } +} +testNativeFunction(); + +// Note: Typically won't use LoadFunctionLengthResult, because the "length" +// property will be resolved on the first access. +function testSelfHostedFunction() { + for (var i = 0; i < 200; ++i) { + assertEq(Array.prototype.forEach.length, 1); + } +} +testSelfHostedFunction(); + +// Bailout when the length doesn't fit into int32. +function testBailoutLength() { + var values = [0, 0x80000000]; + var bound = empty.bind(); + + for (var i = 0; i < 10; ++i) { + var value = values[0 + (i >= 5)]; + + // Define on each iteration to get the same shape. + Object.defineProperty(bound, "length", {value}); + + for (var j = 0; j < 100; ++j) { + var f = bound.bind(); + assertEq(f.length, value); + } + } +} +testBailoutLength(); + +// Bailout when trying to read "length" from a property with a lazy script. +function testBailoutLazyFunction() { + for (var i = 0; i < 200; ++i) { + var values = [function(){}, function(a){}]; + var index = 0 + (i >= 100); + assertEq(values[index].length, index); + } +} +testBailoutLazyFunction(); + +// Bailout when trying to read "length" from a property with a lazy self-hosted script. +function testBailoutLazySelfHostedFunction() { + for (var i = 0; i < 200; ++i) { + var values = [function(){}, Array.prototype.map]; + var index = 0 + (i >= 100); + assertEq(values[index].length, index); + } +} +testBailoutLazySelfHostedFunction(); diff --git a/js/src/jit-test/tests/warp/function-load-name.js b/js/src/jit-test/tests/warp/function-load-name.js new file mode 100644 index 0000000000..686ed32629 --- /dev/null +++ b/js/src/jit-test/tests/warp/function-load-name.js @@ -0,0 +1,100 @@ +// Test transpiling of LoadFunctionNameResult and cover possible bailout conditions. + +function empty() {} + +// Note: Typically won't use LoadFunctionNameResult, because the "name" +// property will be resolved on the first access. +function testGlobalFunction() { + for (var i = 0; i < 200; ++i) { + assertEq(empty.name, "empty"); + } +} +testGlobalFunction(); + +// Note: Typically won't use LoadFunctionNameResult, because the "name" +// property will be resolved on the first access. +function testInnerFunction() { + function f() {} + for (var i = 0; i < 200; ++i) { + assertEq(f.name, "f"); + } +} +testInnerFunction(); + +function testPerLoopFunction() { + for (var i = 0; i < 200; ++i) { + assertEq(function f(){}.name, "f"); + } +} +testPerLoopFunction(); + +// Note: Typically won't use LoadFunctionNameResult, because the "name" +// property will be resolved on the first access. +function testNativeFunction() { + for (var i = 0; i < 200; ++i) { + assertEq(Math.max.name, "max"); + } +} +testNativeFunction(); + +// Note: Typically won't use LoadFunctionNameResult, because the "name" +// property will be resolved on the first access. +function testSelfHostedFunction() { + for (var i = 0; i < 200; ++i) { + assertEq(Array.prototype.forEach.name, "forEach"); + } +} +testSelfHostedFunction(); + +// Bailout when the name property is resolved. +function testBailoutResolvedName() { + function f1() {} + + // Ensure the name property of |f1| is resolved. + assertEq(f1.name, "f1"); + + var names = ["f", "f1"]; + + for (var i = 0; i < 10; ++i) { + var name = names[0 + (i >= 5)]; + + for (var j = 0; j < 100; ++j) { + var values = [function f(){}, f1]; + var value = values[0 + (i >= 5)]; + + assertEq(value.name, name); + } + } +} +testBailoutResolvedName(); + +// Bailout when the HAS_BOUND_FUNCTION_NAME_PREFIX isn't set. +function testBailoutBoundName() { + function f1() {} + function f2() {} + + var bound = f1.bind(); + + // Ensure the name property of |bound| is resolved. That way new functions + // created through |bound().bind()| will have the HAS_BOUND_FUNCTION_NAME_PREFIX + // flag set. + assertEq(bound.name, "bound f1"); + + // |bound1| and |bound2| have the same shape, but different function flags. + var bound1 = bound.bind(); // HAS_BOUND_FUNCTION_NAME_PREFIX + var bound2 = f2.bind(); // ! HAS_BOUND_FUNCTION_NAME_PREFIX + + var values = [bound1, bound2]; + var names = ["bound bound bound f1", "bound bound f2"]; + + for (var i = 0; i < 10; ++i) { + var value = values[0 + (i >= 5)]; + var name = names[0 + (i >= 5)]; + + for (var j = 0; j < 100; ++j) { + var f = value.bind(); + assertEq(f.name, name); + } + } +} +testBailoutBoundName(); diff --git a/js/src/jit-test/tests/warp/function-var-environment-inlined.js b/js/src/jit-test/tests/warp/function-var-environment-inlined.js new file mode 100644 index 0000000000..f3b9c9b3b7 --- /dev/null +++ b/js/src/jit-test/tests/warp/function-var-environment-inlined.js @@ -0,0 +1,15 @@ +function defaultValue() { return 3; } + +function testCallee(p = defaultValue()) { + var q = p + 1; + return () => q + p; +} +function test() { + var res = 0; + for (var i = 0; i < 2000; i++) { + res += testCallee()(); + res += testCallee(1)(); + } + assertEq(res, 20000); +} +test(); diff --git a/js/src/jit-test/tests/warp/function-var-environment.js b/js/src/jit-test/tests/warp/function-var-environment.js new file mode 100644 index 0000000000..dd23cb67a9 --- /dev/null +++ b/js/src/jit-test/tests/warp/function-var-environment.js @@ -0,0 +1,43 @@ +function defaultValue() { return 123; } + +// 2 environment objects: Call => Var. The lambda uses both of them. +function testBasic(p = defaultValue()) { + for (var i = 0; i < 2000; i++) {} + return () => i + p; +} +assertEq(testBasic()(), 2123); + +function testBailout(p = defaultValue()) { + for (var i = 0; i < 2000; i++) { + if (i > 1950) { + bailout(); + } + } + return () => i + p; +} +assertEq(testBailout()(), 2123); + +// 3 environment objects: Call => Var => Lexical. The lambda uses all of them. +let escaped; +function testVarAndLexical(p = defaultValue()) { + var q = p + 1; + let i = 0; + for (; i < 2000; i++) { + escaped = () => i + p + q; + } +} +testVarAndLexical(); +assertEq(escaped(), 2247); + +function testVarAndLexicalBailout(p = defaultValue()) { + var q = p + 1; + let i = 0; + for (; i < 2000; i++) { + escaped = () => i + p - q; + if (i > 1950) { + bailout(); + } + } +} +testVarAndLexicalBailout(); +assertEq(escaped(), 1999); diff --git a/js/src/jit-test/tests/warp/guard-function-is-non-builtin-ctor.js b/js/src/jit-test/tests/warp/guard-function-is-non-builtin-ctor.js new file mode 100644 index 0000000000..c2b92d4510 --- /dev/null +++ b/js/src/jit-test/tests/warp/guard-function-is-non-builtin-ctor.js @@ -0,0 +1,20 @@ +function test() { + for (var i = 0; i <= 200; ++i) { + // Create a fresh function in each iteration. + var values = [function(){}, () => {}]; + + // Use an arrow function in the last iteration. + var useArrowFn = (i === 200); + + // No conditional (?:) so we don't trigger a cold-code bailout. + var value = values[0 + useArrowFn]; + + // Set or create the "prototype" property. + value.prototype = null; + + // The "prototype" is configurable iff the function is an arrow function. + var desc = Object.getOwnPropertyDescriptor(value, "prototype"); + assertEq(desc.configurable, useArrowFn); + } +} +test(); diff --git a/js/src/jit-test/tests/warp/guard-has-getter-setter.js b/js/src/jit-test/tests/warp/guard-has-getter-setter.js new file mode 100644 index 0000000000..dc4b1d9ebd --- /dev/null +++ b/js/src/jit-test/tests/warp/guard-has-getter-setter.js @@ -0,0 +1,263 @@ +// Access property once. +function simple() { + var obj = { + get p() { + return 1; + } + }; + + // Use objects with different shapes to enter megamorphic state for + // the JSOp::GetProp opcode. + var array = [ + Object.create(obj, {a: {value: 1}}), + Object.create(obj, {b: {value: 2}}), + Object.create(obj, {c: {value: 3}}), + Object.create(obj, {d: {value: 4}}), + Object.create(obj, {e: {value: 5}}), + Object.create(obj, {f: {value: 6}}), + Object.create(obj, {g: {value: 7}}), + Object.create(obj, {h: {value: 8}}), + ]; + + var r = 0; + for (var i = 0; i < 200; ++i) { + var o = array[i & 7]; + r += o.p; + } + assertEq(r, 200); +} +simple(); + +// Access property multiple times (consecutive) to test that MGuardHasGetterSetter +// ops can be merged. +function consecutive() { + var obj = { + get p() { + return 1; + } + }; + + // Use objects with different shapes to enter megamorphic state for + // the JSOp::GetProp opcode. + var array = [ + Object.create(obj, {a: {value: 1}}), + Object.create(obj, {b: {value: 2}}), + Object.create(obj, {c: {value: 3}}), + Object.create(obj, {d: {value: 4}}), + Object.create(obj, {e: {value: 5}}), + Object.create(obj, {f: {value: 6}}), + Object.create(obj, {g: {value: 7}}), + Object.create(obj, {h: {value: 8}}), + ]; + + var r = 0; + for (var i = 0; i < 200; ++i) { + var o = array[i & 7]; + + r += o.p; + r += o.p; + r += o.p; + r += o.p; + } + assertEq(r, 4 * 200); +} +consecutive(); + +// Access property multiple times (loop) to test LICM. +function loop() { + var obj = { + get p() { + return 1; + } + }; + + // Use objects with different shapes to enter megamorphic state for + // the JSOp::GetProp opcode. + var array = [ + Object.create(obj, {a: {value: 1}}), + Object.create(obj, {b: {value: 2}}), + Object.create(obj, {c: {value: 3}}), + Object.create(obj, {d: {value: 4}}), + Object.create(obj, {e: {value: 5}}), + Object.create(obj, {f: {value: 6}}), + Object.create(obj, {g: {value: 7}}), + Object.create(obj, {h: {value: 8}}), + ]; + + var r = 0; + for (var i = 0; i < 200; ++i) { + var o = array[i & 7]; + + for (var j = 0; j < 5; ++j) { + r += o.p; + } + } + assertEq(r, 5 * 200); +} +loop(); + +// Bailout when prototype changes. +function modifyProto() { + var obj = { + get p() { + return 1; + } + }; + + var obj2 = { + get p() { + return 2; + } + }; + + // Use objects with different shapes to enter megamorphic state for + // the JSOp::GetProp opcode. + var array = [ + Object.create(obj, {a: {value: 1}}), + Object.create(obj, {b: {value: 2}}), + Object.create(obj, {c: {value: 3}}), + Object.create(obj, {d: {value: 4}}), + Object.create(obj, {e: {value: 5}}), + Object.create(obj, {f: {value: 6}}), + Object.create(obj, {g: {value: 7}}), + Object.create(obj, {h: {value: 8}}), + ]; + + var r = 0; + for (var i = 0; i < 200; ++i) { + var o = array[i & 7]; + + r += o.p; + + // Always execute Object.setPrototypeOf() to avoid cold code bailouts, + // which would happen for conditional code like if-statements. But only + // actually change |o|'s prototype once. + var j = (i === 100) | 0; + var q = [{}, o][j]; + Object.setPrototypeOf(q, obj2); + + r += o.p; + } + assertEq(r, 2 * 200 + Math.floor(100 / 8) * 2 + 1); +} +modifyProto(); + +// Bailout when property is changed to own data property. +function modifyToOwnValue() { + var obj = { + get p() { + return 1; + } + }; + + // Use objects with different shapes to enter megamorphic state for + // the JSOp::GetProp opcode. + var array = [ + Object.create(obj, {a: {value: 1}}), + Object.create(obj, {b: {value: 2}}), + Object.create(obj, {c: {value: 3}}), + Object.create(obj, {d: {value: 4}}), + Object.create(obj, {e: {value: 5}}), + Object.create(obj, {f: {value: 6}}), + Object.create(obj, {g: {value: 7}}), + Object.create(obj, {h: {value: 8}}), + ]; + + var r = 0; + for (var i = 0; i < 200; ++i) { + var o = array[i & 7]; + + r += o.p; + + // Always execute Object.setPrototypeOf() to avoid cold code bailouts, + // which would happen for conditional code like if-statements. But only + // actually change |o|'s prototype once. + var j = (i === 100) | 0; + var q = [{}, o][j]; + Object.defineProperty(q, "p", {value: 2}); + + r += o.p; + } + assertEq(r, 2 * 200 + Math.floor(100 / 8) * 2 + 1); +} +modifyToOwnValue(); + +// Bailout when property is changed to own accessor property. +function modifyToOwnAccessor() { + var obj = { + get p() { + return 1; + } + }; + + // Use objects with different shapes to enter megamorphic state for + // the JSOp::GetProp opcode. + var array = [ + Object.create(obj, {a: {value: 1}}), + Object.create(obj, {b: {value: 2}}), + Object.create(obj, {c: {value: 3}}), + Object.create(obj, {d: {value: 4}}), + Object.create(obj, {e: {value: 5}}), + Object.create(obj, {f: {value: 6}}), + Object.create(obj, {g: {value: 7}}), + Object.create(obj, {h: {value: 8}}), + ]; + + var r = 0; + for (var i = 0; i < 200; ++i) { + var o = array[i & 7]; + + r += o.p; + + // Always execute Object.setPrototypeOf() to avoid cold code bailouts, + // which would happen for conditional code like if-statements. But only + // actually change |o|'s prototype once. + var j = (i === 100) | 0; + var q = [{}, o][j]; + Object.defineProperty(q, "p", {get() { return 2; }}); + + r += o.p; + } + assertEq(r, 2 * 200 + Math.floor(100 / 8) * 2 + 1); +} +modifyToOwnAccessor(); + +// Bailout when changing accessor. +function modifyProtoAccessor() { + var obj = { + get p() { + return 1; + } + }; + + // Use objects with different shapes to enter megamorphic state for + // the JSOp::GetProp opcode. + var array = [ + Object.create(obj, {a: {value: 1}}), + Object.create(obj, {b: {value: 2}}), + Object.create(obj, {c: {value: 3}}), + Object.create(obj, {d: {value: 4}}), + Object.create(obj, {e: {value: 5}}), + Object.create(obj, {f: {value: 6}}), + Object.create(obj, {g: {value: 7}}), + Object.create(obj, {h: {value: 8}}), + ]; + + var r = 0; + for (var i = 0; i < 200; ++i) { + var o = array[i & 7]; + + r += o.p; + + // Always execute Object.setPrototypeOf() to avoid cold code bailouts, + // which would happen for conditional code like if-statements. But only + // actually change |o|'s prototype once. + var j = (i === 100) | 0; + var q = [{}, obj][j]; + Object.defineProperty(q, "p", {get() { return 2; }}); + + r += o.p; + } + assertEq(r, 2 * 200 + 100 * 2 - 1); +} +modifyProtoAccessor(); diff --git a/js/src/jit-test/tests/warp/guard-specific-atom-with-short-atom.js b/js/src/jit-test/tests/warp/guard-specific-atom-with-short-atom.js new file mode 100644 index 0000000000..84be75fbfd --- /dev/null +++ b/js/src/jit-test/tests/warp/guard-specific-atom-with-short-atom.js @@ -0,0 +1,82 @@ +// Test case to cover constant atom guards. +// +// GuardSpecificAtom for short (≤32 characters) constant atoms is optimised. + +function* characters(...ranges) { + for (let [start, end] of ranges) { + for (let i = start; i <= end; ++i) { + yield i; + } + } +} + +const ascii = [...characters( + [0x41, 0x5A], // A..Z + [0x61, 0x7A], // a..z + [0x30, 0x39], // 0..9 +)]; + +const latin1 = [...characters( + [0xC0, 0xFF], // À..ÿ +)]; + +const twoByte = [...characters( + [0x100, 0x17E], // Ā..ž +)]; + +function toRope(s) { + try { + return newRope(s[0], s.substring(1)); + } catch {} + // newRope can fail when |s| fits into an inline string. In that case simply + // return the input. + return s; +} + +function atomize(s) { + return Object.keys({[s]: 0})[0]; +} + +for (let i = 1; i <= 32; ++i) { + let strings = [ascii, latin1, twoByte].flatMap(codePoints => [ + // Same string as the input. + String.fromCodePoint(...codePoints.slice(0, i)), + + // Same length as the input, but a different string. + String.fromCodePoint(...codePoints.slice(1, i + 1)), + + // Shorter string than the input. + String.fromCodePoint(...codePoints.slice(0, i - 1)), + + // Longer string than the input. + String.fromCodePoint(...codePoints.slice(0, i + 1)), + ]).flatMap(x => [ + x, + toRope(x), + newString(x, {twoByte: true}), + atomize(x), + ]); + + // Must be small enough to transition to megamorphic ICs. + const stringsPerLoop = 4; + + for (let codePoints of [ascii, latin1, twoByte]) { + let str = String.fromCodePoint(...codePoints.slice(0, i)); + + for (let i = 0; i < strings.length; i += stringsPerLoop) { + let fn = Function("strings", ` + var obj = {["${str}"]: 0}; + + for (let i = 0; i < 250; ++i) { + let idx = i % strings.length; + let str = strings[idx]; + + let actual = str in obj; + let expected = str === "${str}"; + if (actual !== expected) throw new Error(); + } + `); + fn(strings.slice(i, stringsPerLoop)); + } + } +} diff --git a/js/src/jit-test/tests/warp/guard-string-to-number-or-int32.js b/js/src/jit-test/tests/warp/guard-string-to-number-or-int32.js new file mode 100644 index 0000000000..8a28d46f99 --- /dev/null +++ b/js/src/jit-test/tests/warp/guard-string-to-number-or-int32.js @@ -0,0 +1,36 @@ +function stringToNumber() { + function f(s) { + return ~~s; + } + + var q = 0; + for (var i = 0; i < 200; ++i) { + q += f("1"); + q += f("0x2"); + q += f("0b11"); + q += f("0o4"); + + // Invalid inputs: ~~Nan == 0 + q += f("z"); + q += f("0x2.3"); + q += f("0x1.fp4"); + } + assertEq(q, (1 + 2 + 3 + 4) * 200); +} +stringToNumber(); + +function stringToInt32() { + function f(s) { + return s - 0; + } + + var q = 0; + for (var i = 0; i < 200; ++i) { + q += f("1"); + q += f("0x2"); + q += f("0b11"); + q += f("0o4"); + } + assertEq(q, (1 + 2 + 3 + 4) * 200); +} +stringToInt32(); diff --git a/js/src/jit-test/tests/warp/guardproto-nursery.js b/js/src/jit-test/tests/warp/guardproto-nursery.js new file mode 100644 index 0000000000..5093298e88 --- /dev/null +++ b/js/src/jit-test/tests/warp/guardproto-nursery.js @@ -0,0 +1,13 @@ +function f() { + var o = {x: 1, y: 3}; + o.__proto__ = {x: 2}; + var p = Math; + p.__proto__ = o; + p.__proto__ = {__proto__: o}; + + for (var i = 0; i < 3000; i++) { + assertEq(p.x, 1); + assertEq(p.y, 3); + } +} +f(); diff --git a/js/src/jit-test/tests/warp/inline-array-at.js b/js/src/jit-test/tests/warp/inline-array-at.js new file mode 100644 index 0000000000..4abc06ebd5 --- /dev/null +++ b/js/src/jit-test/tests/warp/inline-array-at.js @@ -0,0 +1,15 @@ +function f(x) { + assertEq(x.at(0), 1); + assertEq(x.at(-1), 3); + assertEq(x.at(10), undefined); +} + +function g() { + for (var i = 0; i < 100; i++) { + f([1, 2, 3]); + } +} + +for (var j = 0; j < 10; j++) { + g(); +} diff --git a/js/src/jit-test/tests/warp/inlined-accessor-exc-bailout.js b/js/src/jit-test/tests/warp/inlined-accessor-exc-bailout.js new file mode 100644 index 0000000000..9346ac65bb --- /dev/null +++ b/js/src/jit-test/tests/warp/inlined-accessor-exc-bailout.js @@ -0,0 +1,45 @@ +// |jit-test| --fast-warmup + +// Tests for exception bailout from inlined getter/setter. + +function throwingGetter() { + var o = {}; + var count = 0; + Object.defineProperty(o, "getter", {get: function() { + if (count++ === 195) { + throw 1; + } + }}); + var ex = null; + try { + for (var i = 0; i < 200; i++) { + o.getter; + } + } catch(e) { + ex = e; + } + assertEq(ex, 1); + assertEq(count, 196); +} +throwingGetter(); + +function throwingSetter() { + var o = {}; + var count = 0; + Object.defineProperty(o, "setter", {set: function(v) { + if (count++ === 195) { + throw 1; + } + }}); + var ex = null; + try { + for (var i = 0; i < 200; i++) { + o.setter = i; + } + } catch(e) { + ex = e; + } + assertEq(ex, 1); + assertEq(count, 196); +} +throwingSetter(); diff --git a/js/src/jit-test/tests/warp/load-unboxed-typedarray-bigint.js b/js/src/jit-test/tests/warp/load-unboxed-typedarray-bigint.js new file mode 100644 index 0000000000..736e80f3f5 --- /dev/null +++ b/js/src/jit-test/tests/warp/load-unboxed-typedarray-bigint.js @@ -0,0 +1,7 @@ +var ta = new BigInt64Array([0n, 1n]); +var q = 0; +for (var i = 0; i < 10000; ++i) { + if (ta[i&1]) q++; +} + +assertEq(q, 5000); diff --git a/js/src/jit-test/tests/warp/map-get-bigint.js b/js/src/jit-test/tests/warp/map-get-bigint.js new file mode 100644 index 0000000000..3b746fb81c --- /dev/null +++ b/js/src/jit-test/tests/warp/map-get-bigint.js @@ -0,0 +1,211 @@ +// Similar test as "cacheir/map-get-bigint.js", except that we now perform +// duplicate lookups to ensure GVN works properly. + +// Return a new map, possibly filling some dummy entries to enforce creating +// multiple hash buckets. +function createMap(values, n) { + var xs = [...values]; + for (var i = 0; i < n; ++i) { + xs.push({}); + } + return new Map(xs.map((x, i) => [x, i + 1])); +} + +function runTest(fn) { + fn(0); + fn(100); +} + +function testInlineDigitsSameSign_same_map(n) { + var xs = [1n, 2n]; + var ys = [3n, 4n]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map.get(z); + if (v !== undefined) c += v; + var w = map.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testInlineDigitsSameSign_same_map); + +function testInlineDigitsDifferentSign_same_map(n) { + var xs = [-1n, 2n]; + var ys = [1n, -2n]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map.get(z); + if (v !== undefined) c += v; + var w = map.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testInlineDigitsDifferentSign_same_map); + +function testHeapDigitsSameSign_same_map(n) { + // Definitely uses heap digits. + var heap = 2n ** 1000n; + + var xs = [heap + 1n, heap + 2n]; + var ys = [heap + 3n, heap + 4n]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map.get(z); + if (v !== undefined) c += v; + var w = map.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testHeapDigitsSameSign_same_map); + +function testHeapDigitsDifferentSign_same_map(n) { + // Definitely uses heap digits. + var heap = 2n ** 1000n; + + var xs = [-(heap + 1n), heap + 2n]; + var ys = [heap + 1n, -(heap + 2n)]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map.get(z); + if (v !== undefined) c += v; + var w = map.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testHeapDigitsDifferentSign_same_map); + +// Duplicate the above tests, but this time use a different map. + +function testInlineDigitsSameSign_different_map(n) { + var xs = [1n, 2n]; + var ys = [3n, 4n]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map1.get(z); + if (v !== undefined) c += v; + var w = map2.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testInlineDigitsSameSign_different_map); + +function testInlineDigitsDifferentSign_different_map(n) { + var xs = [-1n, 2n]; + var ys = [1n, -2n]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map1.get(z); + if (v !== undefined) c += v; + var w = map2.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testInlineDigitsDifferentSign_different_map); + +function testHeapDigitsSameSign_different_map(n) { + // Definitely uses heap digits. + var heap = 2n ** 1000n; + + var xs = [heap + 1n, heap + 2n]; + var ys = [heap + 3n, heap + 4n]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map1.get(z); + if (v !== undefined) c += v; + var w = map2.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testHeapDigitsSameSign_different_map); + +function testHeapDigitsDifferentSign_different_map(n) { + // Definitely uses heap digits. + var heap = 2n ** 1000n; + + var xs = [-(heap + 1n), heap + 2n]; + var ys = [heap + 1n, -(heap + 2n)]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map1.get(z); + if (v !== undefined) c += v; + var w = map2.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testHeapDigitsDifferentSign_different_map); + +// Test the alias information is correct. + +function test_alias(n) { + var xs = [1n, 2n]; + var map = createMap([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + map.set(x, 1); + var v = map.get(x); + + map.delete(x); + var w = map.get(x); + + c += v; + assertEq(w, undefined); + } + assertEq(c, N); +} +runTest(test_alias); diff --git a/js/src/jit-test/tests/warp/map-get-nongcthing.js b/js/src/jit-test/tests/warp/map-get-nongcthing.js new file mode 100644 index 0000000000..3a0e0f7632 --- /dev/null +++ b/js/src/jit-test/tests/warp/map-get-nongcthing.js @@ -0,0 +1,343 @@ +// Similar test as "cacheir/map-get-nongcthing.js", except that we now perform +// duplicate lookups to ensure GVN works properly. + +// Return a new map, possibly filling some dummy entries to enforce creating +// multiple hash buckets. +function createMap(values, n) { + var xs = [...values]; + for (var i = 0; i < n; ++i) { + xs.push({}); + } + return new Map(xs.map((x, i) => [x, i + 1])); +} + +function runTest(fn) { + fn(0); + fn(100); +} + +function testInt32_same_map(n) { + var xs = [1, 2]; + var ys = [3, 4]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map.get(z); + if (v !== undefined) c += v; + var w = map.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testInt32_same_map); + +function testDouble_same_map(n) { + var xs = [Math.PI, Infinity]; + var ys = [Math.E, -Infinity]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map.get(z); + if (v !== undefined) c += v; + var w = map.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testDouble_same_map); + +function testZero_same_map(n) { + var xs = [0, -0]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var map = createMap([0], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map.get(z); + if (v !== undefined) c += v; + var w = map.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N); +} +runTest(testZero_same_map); + +function testNaN_same_map(n) { + var xs = [NaN, -NaN]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var map = createMap([NaN], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map.get(z); + if (v !== undefined) c += v; + var w = map.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N); +} +runTest(testNaN_same_map); + +function testUndefinedAndNull_same_map(n) { + var xs = [undefined, null]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map.get(z); + if (v !== undefined) c += v; + var w = map.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testUndefinedAndNull_same_map); + +function testBoolean_same_map(n) { + var xs = [true, false]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map.get(z); + if (v !== undefined) c += v; + var w = map.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testBoolean_same_map); + +// Duplicate the above tests, but this time use a different map. + +function testInt32_different_map(n) { + var xs = [1, 2]; + var ys = [3, 4]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map1.get(z); + if (v !== undefined) c += v; + var w = map2.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testInt32_different_map); + +function testDouble_different_map(n) { + var xs = [Math.PI, Infinity]; + var ys = [Math.E, -Infinity]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map1.get(z); + if (v !== undefined) c += v; + var w = map2.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testDouble_different_map); + +function testZero_different_map(n) { + var xs = [0, -0]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var map1 = createMap([0], n); + var map2 = createMap([0], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map1.get(z); + if (v !== undefined) c += v; + var w = map2.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N); +} +runTest(testZero_different_map); + +function testNaN_different_map(n) { + var xs = [NaN, -NaN]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var map1 = createMap([NaN], n); + var map2 = createMap([NaN], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map1.get(z); + if (v !== undefined) c += v; + var w = map2.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N); +} +runTest(testNaN_different_map); + +function testUndefinedAndNull_different_map(n) { + var xs = [undefined, null]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map1.get(z); + if (v !== undefined) c += v; + var w = map2.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testUndefinedAndNull_different_map); + +function testBoolean_different_map(n) { + var xs = [true, false]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map1.get(z); + if (v !== undefined) c += v; + var w = map2.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testBoolean_different_map); + +// Test the alias information is correct. + +function testInt32_alias(n) { + var xs = [1, 2]; + var map = createMap([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + map.set(x, 1); + var v = map.get(x); + + map.delete(x); + var w = map.get(x); + + c += v; + assertEq(w, undefined); + } + assertEq(c, N); +} +runTest(testInt32_alias); + +function testDouble_alias(n) { + var xs = [Math.PI, Infinity]; + var map = createMap([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + map.set(x, 1); + var v = map.get(x); + + map.delete(x); + var w = map.get(x); + + c += v; + assertEq(w, undefined); + } + assertEq(c, N); +} +runTest(testDouble_alias); + +function testUndefinedAndNull_alias(n) { + var xs = [undefined, null]; + var map = createMap([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + map.set(x, 1); + var v = map.get(x); + + map.delete(x); + var w = map.get(x); + + c += v; + assertEq(w, undefined); + } + assertEq(c, N); +} +runTest(testUndefinedAndNull_alias); + +function testBoolean_alias(n) { + var xs = [true, false]; + var map = createMap([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + map.set(x, 1); + var v = map.get(x); + + map.delete(x); + var w = map.get(x); + + c += v; + assertEq(w, undefined); + } + assertEq(c, N); +} +runTest(testBoolean_alias); diff --git a/js/src/jit-test/tests/warp/map-get-object.js b/js/src/jit-test/tests/warp/map-get-object.js new file mode 100644 index 0000000000..0c13b6e50a --- /dev/null +++ b/js/src/jit-test/tests/warp/map-get-object.js @@ -0,0 +1,104 @@ +// Similar test as "cacheir/map-get-object.js", except that we now perform +// duplicate lookups to ensure GVN works properly. + +// Return a new map, possibly filling some dummy entries to enforce creating +// multiple hash buckets. +function createMap(values, n) { + var xs = [...values]; + for (var i = 0; i < n; ++i) { + xs.push({}); + } + return new Map(xs.map((x, i) => [x, i + 1])); +} + +function runTest(fn) { + fn(0); + fn(100); +} + +function test_same_map(n) { + var xs = [{}, {}]; + var ys = [{}, {}]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map.get(z); + if (v !== undefined) c += v; + var w = map.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(test_same_map); + +// Duplicate the above tests, but this time use a different map. + +function test_different_map(n) { + var xs = [{}, {}]; + var ys = [{}, {}]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map1.get(z); + if (v !== undefined) c += v; + var w = map2.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(test_different_map); + +// Test the alias information is correct. + +function test_alias(n) { + var xs = [{}, {}]; + var map = createMap([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + map.set(x, 1); + var v = map.get(x); + + map.delete(x); + var w = map.get(x); + + c += v; + assertEq(w, undefined); + } + assertEq(c, N); +} +runTest(test_alias); + +// And finally test that we don't actually support GVN for objects, because the +// hash changes when moving an object. + +function testRekey() { + var map = new Map(); + var c = 0; + var N = 100; + for (var i = 0; i < N; ++i) { + var k = {}; + map.set(k, 1); + + c += map.get(k); + + minorgc(); + + c += map.get(k); + } + + assertEq(c, N * 2); +} +testRekey(); diff --git a/js/src/jit-test/tests/warp/map-get-string.js b/js/src/jit-test/tests/warp/map-get-string.js new file mode 100644 index 0000000000..265c1caf94 --- /dev/null +++ b/js/src/jit-test/tests/warp/map-get-string.js @@ -0,0 +1,162 @@ +// Similar test as "cacheir/map-get-string.js", except that we now perform +// duplicate lookups to ensure GVN works properly. + +// Return a new map, possibly filling some dummy entries to enforce creating +// multiple hash buckets. +function createMap(values, n) { + var xs = [...values]; + for (var i = 0; i < n; ++i) { + xs.push({}); + } + return new Map(xs.map((x, i) => [x, i + 1])); +} + +function runTest(fn) { + fn(0); + fn(100); +} + +function testConstant_different_map(n) { + var xs = ["a", "b"]; + var ys = ["c", "d"]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map.get(z); + if (v !== undefined) c += v; + var w = map.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testConstant_different_map); + +function testComputed_different_map(n) { + var xs = ["a", "b"]; + var ys = ["c", "d"]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + z = String.fromCharCode(z.charCodeAt(0)); + var v = map.get(z); + if (v !== undefined) c += v; + var w = map.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testComputed_different_map); + +function testRope_different_map(n) { + var xs = ["a", "b"]; + var ys = ["c", "d"]; + var zs = [...xs, ...ys]; + var map = createMap(xs.map(x => x.repeat(100)), n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3].repeat(100); + var v = map.get(z); + if (v !== undefined) c += v; + var w = map.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testRope_different_map); + +// Duplicate the above tests, but this time use a different map. + +function testConstant_different_map(n) { + var xs = ["a", "b"]; + var ys = ["c", "d"]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map1.get(z); + if (v !== undefined) c += v; + var w = map2.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testConstant_different_map); + +function testComputed_different_map(n) { + var xs = ["a", "b"]; + var ys = ["c", "d"]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + z = String.fromCharCode(z.charCodeAt(0)); + var v = map1.get(z); + if (v !== undefined) c += v; + var w = map2.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testComputed_different_map); + +function testRope_different_map(n) { + var xs = ["a", "b"]; + var ys = ["c", "d"]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs.map(x => x.repeat(100)), n); + var map2 = createMap(xs.map(x => x.repeat(100)), n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3].repeat(100); + var v = map1.get(z); + if (v !== undefined) c += v; + var w = map2.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(testRope_different_map); + +// Test the alias information is correct. + +function test_alias(n) { + var xs = ["a", "b"]; + var map = createMap([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + map.set(x, 1); + var v = map.get(x); + + map.delete(x); + var w = map.get(x); + + c += v; + assertEq(w, undefined); + } + assertEq(c, N); +} +runTest(test_alias); diff --git a/js/src/jit-test/tests/warp/map-get-symbol.js b/js/src/jit-test/tests/warp/map-get-symbol.js new file mode 100644 index 0000000000..358f79ef96 --- /dev/null +++ b/js/src/jit-test/tests/warp/map-get-symbol.js @@ -0,0 +1,82 @@ +// Similar test as "cacheir/map-get-symbol.js", except that we now perform +// duplicate lookups to ensure GVN works properly. + +// Return a new map, possibly filling some dummy entries to enforce creating +// multiple hash buckets. +function createMap(values, n) { + var xs = [...values]; + for (var i = 0; i < n; ++i) { + xs.push({}); + } + return new Map(xs.map((x, i) => [x, i + 1])); +} + +function runTest(fn) { + fn(0); + fn(100); +} + +function test_same_map(n) { + var xs = [Symbol(), Symbol()]; + var ys = [Symbol(), Symbol()]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map.get(z); + if (v !== undefined) c += v; + var w = map.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(test_same_map); + +// Duplicate the above tests, but this time use a different map. + +function test_different_map(n) { + var xs = [Symbol(), Symbol()]; + var ys = [Symbol(), Symbol()]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + var v = map1.get(z); + if (v !== undefined) c += v; + var w = map2.get(z); + if (w !== undefined) c += w; + } + assertEq(c, N + N / 2); +} +runTest(test_different_map); + +// Test the alias information is correct. + +function test_alias(n) { + var xs = [Symbol(), Symbol()]; + var map = createMap([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + map.set(x, 1); + var v = map.get(x); + + map.delete(x); + var w = map.get(x); + + c += v; + assertEq(w, undefined); + } + assertEq(c, N); +} +runTest(test_alias); diff --git a/js/src/jit-test/tests/warp/map-get-value.js b/js/src/jit-test/tests/warp/map-get-value.js new file mode 100644 index 0000000000..525ee4fc9b --- /dev/null +++ b/js/src/jit-test/tests/warp/map-get-value.js @@ -0,0 +1,104 @@ +// Similar test as "cacheir/map-get-value.js", except that we now perform +// duplicate lookups to ensure GVN works properly. + +// Return a new map, possibly filling some dummy entries to enforce creating +// multiple hash buckets. +function createMap(values, n) { + var xs = [...values]; + for (var i = 0; i < n; ++i) { + xs.push({}); + } + return new Map(xs.map((x, i) => [x, i + 1])); +} + +function runTest(fn) { + fn(0); + fn(100); +} + +function testPolymorphic_same_map(n) { + var xs = [10, 10.5, "test", Symbol("?"), 123n, -123n, {}, []]; + var ys = [-0, NaN, "bad", Symbol("!"), 42n, -99n, {}, []]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 128; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 15]; + var v = map.get(z); + if (v !== undefined) c += v; + var w = map.get(z); + if (w !== undefined) c += w; + } + assertEq(c, (8 * 9) / 2 * 8 * 2); +} +runTest(testPolymorphic_same_map); + +// Duplicate the above tests, but this time use a different map. + +function testPolymorphic_different_map(n) { + var xs = [10, 10.5, "test", Symbol("?"), 123n, -123n, {}, []]; + var ys = [-0, NaN, "bad", Symbol("!"), 42n, -99n, {}, []]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 128; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 15]; + var v = map1.get(z); + if (v !== undefined) c += v; + var w = map2.get(z); + if (w !== undefined) c += w; + } + assertEq(c, (8 * 9) / 2 * 8 * 2); +} +runTest(testPolymorphic_different_map); + +// Test the alias information is correct. + +function testPolymorphic_alias(n) { + var xs = [10, 10.5, "test", Symbol("?"), 123n, -123n, {}, []]; + var map = createMap([], n); + + var N = 128; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + map.set(x, 1); + var v = map.get(x); + + map.delete(x); + var w = map.get(x); + + c += v; + assertEq(w, undefined); + } + assertEq(c, N); +} +runTest(testPolymorphic_alias); + +// And finally test that we don't actually support GVN for values, because the +// hash changes when moving a value which holds an object. + +function testRekey() { + var map = new Map(); + var c = 0; + var N = 100; + for (var i = 0; i < N; ++i) { + var k = (i & 1) ? {} : null; + map.set(k, 1); + + c += map.get(k); + + minorgc(); + + c += map.get(k); + } + + assertEq(c, N * 2); +} +testRekey(); diff --git a/js/src/jit-test/tests/warp/map-has-bigint.js b/js/src/jit-test/tests/warp/map-has-bigint.js new file mode 100644 index 0000000000..64fd52de55 --- /dev/null +++ b/js/src/jit-test/tests/warp/map-has-bigint.js @@ -0,0 +1,192 @@ +// Similar test as "cacheir/map-has-bigint.js", except that we now perform +// duplicate lookups to ensure GVN works properly. + +// Return a new map, possibly filling some dummy entries to enforce creating +// multiple hash buckets. +function createMap(values, n) { + var xs = [...values]; + for (var i = 0; i < n; ++i) { + xs.push({}); + } + return new Map(xs.map((x, i) => [x, i])); +} + +function runTest(fn) { + fn(0); + fn(100); +} + +function testInlineDigitsSameSign_same_map(n) { + var xs = [1n, 2n]; + var ys = [3n, 4n]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map.has(z)) c++; + if (map.has(z)) c++; + } + assertEq(c, N); +} +runTest(testInlineDigitsSameSign_same_map); + +function testInlineDigitsDifferentSign_same_map(n) { + var xs = [-1n, 2n]; + var ys = [1n, -2n]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map.has(z)) c++; + if (map.has(z)) c++; + } + assertEq(c, N); +} +runTest(testInlineDigitsDifferentSign_same_map); + +function testHeapDigitsSameSign_same_map(n) { + // Definitely uses heap digits. + var heap = 2n ** 1000n; + + var xs = [heap + 1n, heap + 2n]; + var ys = [heap + 3n, heap + 4n]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map.has(z)) c++; + if (map.has(z)) c++; + } + assertEq(c, N); +} +runTest(testHeapDigitsSameSign_same_map); + +function testHeapDigitsDifferentSign_same_map(n) { + // Definitely uses heap digits. + var heap = 2n ** 1000n; + + var xs = [-(heap + 1n), heap + 2n]; + var ys = [heap + 1n, -(heap + 2n)]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map.has(z)) c++; + if (map.has(z)) c++; + } + assertEq(c, N); +} +runTest(testHeapDigitsDifferentSign_same_map); + +// Duplicate the above tests, but this time use a different map. + +function testInlineDigitsSameSign_different_map(n) { + var xs = [1n, 2n]; + var ys = [3n, 4n]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map1.has(z)) c++; + if (map2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testInlineDigitsSameSign_different_map); + +function testInlineDigitsDifferentSign_different_map(n) { + var xs = [-1n, 2n]; + var ys = [1n, -2n]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map1.has(z)) c++; + if (map2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testInlineDigitsDifferentSign_different_map); + +function testHeapDigitsSameSign_different_map(n) { + // Definitely uses heap digits. + var heap = 2n ** 1000n; + + var xs = [heap + 1n, heap + 2n]; + var ys = [heap + 3n, heap + 4n]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map1.has(z)) c++; + if (map2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testHeapDigitsSameSign_different_map); + +function testHeapDigitsDifferentSign_different_map(n) { + // Definitely uses heap digits. + var heap = 2n ** 1000n; + + var xs = [-(heap + 1n), heap + 2n]; + var ys = [heap + 1n, -(heap + 2n)]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map1.has(z)) c++; + if (map2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testHeapDigitsDifferentSign_different_map); + +// Test the alias information is correct. + +function test_alias(n) { + var xs = [1n, 2n]; + var map = createMap([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + map.set(x, x); + if (map.has(x)) c++; + + map.delete(x); + if (map.has(x)) c++; + } + assertEq(c, N); +} +runTest(test_alias); diff --git a/js/src/jit-test/tests/warp/map-has-nongcthing.js b/js/src/jit-test/tests/warp/map-has-nongcthing.js new file mode 100644 index 0000000000..808e1c95b0 --- /dev/null +++ b/js/src/jit-test/tests/warp/map-has-nongcthing.js @@ -0,0 +1,307 @@ +// Similar test as "cacheir/map-has-nongcthing.js", except that we now perform +// duplicate lookups to ensure GVN works properly. + +// Return a new map, possibly filling some dummy entries to enforce creating +// multiple hash buckets. +function createMap(values, n) { + var xs = [...values]; + for (var i = 0; i < n; ++i) { + xs.push({}); + } + return new Map(xs.map((x, i) => [x, i])); +} + +function runTest(fn) { + fn(0); + fn(100); +} + +function testInt32_same_map(n) { + var xs = [1, 2]; + var ys = [3, 4]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map.has(z)) c++; + if (map.has(z)) c++; + } + assertEq(c, N); +} +runTest(testInt32_same_map); + +function testDouble_same_map(n) { + var xs = [Math.PI, Infinity]; + var ys = [Math.E, -Infinity]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map.has(z)) c++; + if (map.has(z)) c++; + } + assertEq(c, N); +} +runTest(testDouble_same_map); + +function testZero_same_map(n) { + var xs = [0, -0]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var map = createMap([0], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map.has(z)) c++; + if (map.has(z)) c++; + } + assertEq(c, N); +} +runTest(testZero_same_map); + +function testNaN_same_map(n) { + var xs = [NaN, -NaN]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var map = createMap([NaN], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map.has(z)) c++; + if (map.has(z)) c++; + } + assertEq(c, N); +} +runTest(testNaN_same_map); + +function testUndefinedAndNull_same_map(n) { + var xs = [undefined, null]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map.has(z)) c++; + if (map.has(z)) c++; + } + assertEq(c, N); +} +runTest(testUndefinedAndNull_same_map); + +function testBoolean_same_map(n) { + var xs = [true, false]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map.has(z)) c++; + if (map.has(z)) c++; + } + assertEq(c, N); +} +runTest(testBoolean_same_map); + +// Duplicate the above tests, but this time use a different map. + +function testInt32_different_map(n) { + var xs = [1, 2]; + var ys = [3, 4]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map1.has(z)) c++; + if (map2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testInt32_different_map); + +function testDouble_different_map(n) { + var xs = [Math.PI, Infinity]; + var ys = [Math.E, -Infinity]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map1.has(z)) c++; + if (map2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testDouble_different_map); + +function testZero_different_map(n) { + var xs = [0, -0]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var map1 = createMap([0], n); + var map2 = createMap([0], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map1.has(z)) c++; + if (map2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testZero_different_map); + +function testNaN_different_map(n) { + var xs = [NaN, -NaN]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var map1 = createMap([NaN], n); + var map2 = createMap([NaN], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map1.has(z)) c++; + if (map2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testNaN_different_map); + +function testUndefinedAndNull_different_map(n) { + var xs = [undefined, null]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map1.has(z)) c++; + if (map2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testUndefinedAndNull_different_map); + +function testBoolean_different_map(n) { + var xs = [true, false]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map1.has(z)) c++; + if (map2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testBoolean_different_map); + +// Test the alias information is correct. + +function testInt32_alias(n) { + var xs = [1, 2]; + var map = createMap([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + map.set(x, x); + if (map.has(x)) c++; + + map.delete(x); + if (map.has(x)) c++; + } + assertEq(c, N); +} +runTest(testInt32_alias); + +function testDouble_alias(n) { + var xs = [Math.PI, Infinity]; + var map = createMap([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + map.set(x, x); + if (map.has(x)) c++; + + map.delete(x); + if (map.has(x)) c++; + } + assertEq(c, N); +} +runTest(testDouble_alias); + +function testUndefinedAndNull_alias(n) { + var xs = [undefined, null]; + var map = createMap([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + map.set(x, x); + if (map.has(x)) c++; + + map.delete(x); + if (map.has(x)) c++; + } + assertEq(c, N); +} +runTest(testUndefinedAndNull_alias); + +function testBoolean_alias(n) { + var xs = [true, false]; + var map = createMap([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + map.set(x, x); + if (map.has(x)) c++; + + map.delete(x); + if (map.has(x)) c++; + } + assertEq(c, N); +} +runTest(testBoolean_alias); diff --git a/js/src/jit-test/tests/warp/map-has-object.js b/js/src/jit-test/tests/warp/map-has-object.js new file mode 100644 index 0000000000..189213d6d4 --- /dev/null +++ b/js/src/jit-test/tests/warp/map-has-object.js @@ -0,0 +1,97 @@ +// Similar test as "cacheir/map-has-object.js", except that we now perform +// duplicate lookups to ensure GVN works properly. + +// Return a new map, possibly filling some dummy entries to enforce creating +// multiple hash buckets. +function createMap(values, n) { + var xs = [...values]; + for (var i = 0; i < n; ++i) { + xs.push({}); + } + return new Map(xs.map((x, i) => [x, i])); +} + +function runTest(fn) { + fn(0); + fn(100); +} + +function test_same_map(n) { + var xs = [{}, {}]; + var ys = [{}, {}]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map.has(z)) c++; + if (map.has(z)) c++; + } + assertEq(c, N); +} +runTest(test_same_map); + +// Duplicate the above tests, but this time use a different map. + +function test_different_map(n) { + var xs = [{}, {}]; + var ys = [{}, {}]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map1.has(z)) c++; + if (map2.has(z)) c++; + } + assertEq(c, N); +} +runTest(test_different_map); + +// Test the alias information is correct. + +function test_alias(n) { + var xs = [{}, {}]; + var map = createMap([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + map.set(x, x); + if (map.has(x)) c++; + + map.delete(x); + if (map.has(x)) c++; + } + assertEq(c, N); +} +runTest(test_alias); + +// And finally test that we don't actually support GVN for objects, because the +// hash changes when moving an object. + +function testRekey() { + var map = new Map(); + var c = 0; + var N = 100; + for (var i = 0; i < N; ++i) { + var k = {}; + map.set(k, i); + + if (map.has(k)) c++; + + minorgc(); + + if (map.has(k)) c++; + } + + assertEq(c, N * 2); +} +testRekey(); diff --git a/js/src/jit-test/tests/warp/map-has-string.js b/js/src/jit-test/tests/warp/map-has-string.js new file mode 100644 index 0000000000..62bca37d22 --- /dev/null +++ b/js/src/jit-test/tests/warp/map-has-string.js @@ -0,0 +1,147 @@ +// Similar test as "cacheir/map-has-string.js", except that we now perform +// duplicate lookups to ensure GVN works properly. + +// Return a new map, possibly filling some dummy entries to enforce creating +// multiple hash buckets. +function createMap(values, n) { + var xs = [...values]; + for (var i = 0; i < n; ++i) { + xs.push({}); + } + return new Map(xs.map((x, i) => [x, i])); +} + +function runTest(fn) { + fn(0); + fn(100); +} + +function testConstant_same_map(n) { + var xs = ["a", "b"]; + var ys = ["c", "d"]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map.has(z)) c++; + if (map.has(z)) c++; + } + assertEq(c, N); +} +runTest(testConstant_same_map); + +function testComputed_same_map(n) { + var xs = ["a", "b"]; + var ys = ["c", "d"]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + z = String.fromCharCode(z.charCodeAt(0)); + if (map.has(z)) c++; + if (map.has(z)) c++; + } + assertEq(c, N); +} +runTest(testComputed_same_map); + +function testRope_same_map(n) { + var xs = ["a", "b"]; + var ys = ["c", "d"]; + var zs = [...xs, ...ys]; + var map = createMap(xs.map(x => x.repeat(100)), n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3].repeat(100); + if (map.has(z)) c++; + if (map.has(z)) c++; + } + assertEq(c, N); +} +runTest(testRope_same_map); + +// Duplicate the above tests, but this time use a different map. + +function testConstant_different_map(n) { + var xs = ["a", "b"]; + var ys = ["c", "d"]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map1.has(z)) c++; + if (map2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testConstant_different_map); + +function testComputed_different_map(n) { + var xs = ["a", "b"]; + var ys = ["c", "d"]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + z = String.fromCharCode(z.charCodeAt(0)); + if (map1.has(z)) c++; + if (map2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testComputed_different_map); + +function testRope_different_map(n) { + var xs = ["a", "b"]; + var ys = ["c", "d"]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs.map(x => x.repeat(100)), n); + var map2 = createMap(xs.map(x => x.repeat(100)), n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3].repeat(100); + if (map1.has(z)) c++; + if (map2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testRope_different_map); + +// Test the alias information is correct. + +function test_alias(n) { + var xs = ["a", "b"]; + var map = createMap([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + map.set(x, x); + if (map.has(x)) c++; + + map.delete(x); + if (map.has(x)) c++; + } + assertEq(c, N); +} +runTest(test_alias); diff --git a/js/src/jit-test/tests/warp/map-has-symbol.js b/js/src/jit-test/tests/warp/map-has-symbol.js new file mode 100644 index 0000000000..8dc8e96781 --- /dev/null +++ b/js/src/jit-test/tests/warp/map-has-symbol.js @@ -0,0 +1,75 @@ +// Similar test as "cacheir/map-has-symbol.js", except that we now perform +// duplicate lookups to ensure GVN works properly. + +// Return a new map, possibly filling some dummy entries to enforce creating +// multiple hash buckets. +function createMap(values, n) { + var xs = [...values]; + for (var i = 0; i < n; ++i) { + xs.push({}); + } + return new Map(xs.map((x, i) => [x, i])); +} + +function runTest(fn) { + fn(0); + fn(100); +} + +function test_same_map(n) { + var xs = [Symbol(), Symbol()]; + var ys = [Symbol(), Symbol()]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map.has(z)) c++; + if (map.has(z)) c++; + } + assertEq(c, N); +} +runTest(test_same_map); + +// Duplicate the above tests, but this time use a different map. + +function test_different_map(n) { + var xs = [Symbol(), Symbol()]; + var ys = [Symbol(), Symbol()]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (map1.has(z)) c++; + if (map2.has(z)) c++; + } + assertEq(c, N); +} +runTest(test_different_map); + +// Test the alias information is correct. + +function test_alias(n) { + var xs = [Symbol(), Symbol()]; + var map = createMap([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + map.set(x, x); + if (map.has(x)) c++; + + map.delete(x); + if (map.has(x)) c++; + } + assertEq(c, N); +} +runTest(test_alias); diff --git a/js/src/jit-test/tests/warp/map-has-value.js b/js/src/jit-test/tests/warp/map-has-value.js new file mode 100644 index 0000000000..b203b762e4 --- /dev/null +++ b/js/src/jit-test/tests/warp/map-has-value.js @@ -0,0 +1,97 @@ +// Similar test as "cacheir/map-has-value.js", except that we now perform +// duplicate lookups to ensure GVN works properly. + +// Return a new map, possibly filling some dummy entries to enforce creating +// multiple hash buckets. +function createMap(values, n) { + var xs = [...values]; + for (var i = 0; i < n; ++i) { + xs.push({}); + } + return new Map(xs.map((x, i) => [x, i])); +} + +function runTest(fn) { + fn(0); + fn(100); +} + +function testPolymorphic_same_map(n) { + var xs = [10, 10.5, "test", Symbol("?"), 123n, -123n, {}, []]; + var ys = [-0, NaN, "bad", Symbol("!"), 42n, -99n, {}, []]; + var zs = [...xs, ...ys]; + var map = createMap(xs, n); + + var N = 128; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 15]; + if (map.has(z)) c++; + if (map.has(z)) c++; + } + assertEq(c, N); +} +runTest(testPolymorphic_same_map); + +// Duplicate the above tests, but this time use a different map. + +function testPolymorphic_different_map(n) { + var xs = [10, 10.5, "test", Symbol("?"), 123n, -123n, {}, []]; + var ys = [-0, NaN, "bad", Symbol("!"), 42n, -99n, {}, []]; + var zs = [...xs, ...ys]; + var map1 = createMap(xs, n); + var map2 = createMap(xs, n); + + var N = 128; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 15]; + if (map1.has(z)) c++; + if (map2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testPolymorphic_different_map); + +// Test the alias information is correct. + +function testPolymorphic_alias(n) { + var xs = [10, 10.5, "test", Symbol("?"), 123n, -123n, {}, []]; + var map = createMap([], n); + + var N = 128; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 15]; + + map.set(x, x); + if (map.has(x)) c++; + + map.delete(x); + if (map.has(x)) c++; + } + assertEq(c, N); +} +runTest(testPolymorphic_alias); + +// And finally test that we don't actually support GVN for values, because the +// hash changes when moving a value which holds an object. + +function testRekey() { + var map = new Map(); + var c = 0; + var N = 100; + for (var i = 0; i < N; ++i) { + var k = (i & 1) ? {} : null; + map.set(k, 1); + + if (map.has(k)) c++; + + minorgc(); + + if (map.has(k)) c++; + } + + assertEq(c, N * 2); +} +testRekey(); diff --git a/js/src/jit-test/tests/warp/math-indirect-truncate.js b/js/src/jit-test/tests/warp/math-indirect-truncate.js new file mode 100644 index 0000000000..9cc967ca33 --- /dev/null +++ b/js/src/jit-test/tests/warp/math-indirect-truncate.js @@ -0,0 +1,55 @@ +function testCeil() { + function ceil(a, b) { + return Math.ceil(a / b) | 0; + } + + // Warm-up + for (var i = 0; i < 50; i++) { + ceil(5, 5); + } + + assertEq(ceil(5, 3), 2); +} +testCeil(); + +function testFloor() { + function floor(a, b) { + return Math.floor(a / b) | 0; + } + + // Warm-up + for (var i = 0; i < 50; i++) { + floor(5, 5); + } + + assertEq(floor(-5, 3), -2); +} +testFloor(); + +function testRound() { + function round(a, b) { + return Math.round(a / b) | 0; + } + + // Warm-up + for (var i = 0; i < 50; i++) { + round(5, 5); + } + + assertEq(round(5, 3), 2); +} +testRound(); + +function testTrunc() { + function trunc(a, b) { + return Math.trunc(a / b) | 0; + } + + // Warm-up + for (var i = 0; i < 50; i++) { + trunc(5, 5); + } + + assertEq(trunc(5, 3), 1); +} +testTrunc(); diff --git a/js/src/jit-test/tests/warp/mega-morphic-load-and-has-prop.js b/js/src/jit-test/tests/warp/mega-morphic-load-and-has-prop.js new file mode 100644 index 0000000000..0d77ceb316 --- /dev/null +++ b/js/src/jit-test/tests/warp/mega-morphic-load-and-has-prop.js @@ -0,0 +1,99 @@ +function testMegamorphicLoadSlot(i) { + var xs = [ + {p: 0}, + {a: 0, p: 1}, + {a: 0, b: 0, p: 2}, + {a: 0, b: 0, c: 0, p: 3}, + {a: 0, b: 0, c: 0, d: 0, p: 4}, + {a: 0, b: 0, c: 0, d: 0, e: 0, p: 5}, + {a: 0, b: 0, c: 0, d: 0, e: 0, f: 0, p: 6}, + {a: 0, b: 0, c: 0, d: 0, e: 0, f: 0, g: 0, p: 7}, + {a: 0, b: 0, c: 0, d: 0, e: 0, f: 0, g: 0, h: 0, p: 8}, + {a: 0, b: 0, c: 0, d: 0, e: 0, f: 0, g: 0, h: 0, i: 0, p: 9}, + ]; + var called = 0; + var obj = { + get p() { + called++; + } + }; + + for (var j = 0; j <= 100; ++j) { + // Don't use if-statements to avoid cold code bailouts. + var x = xs[j % 10]; + var y = [x, obj][(i === 1 && j === 100)|0]; + + // Can't DCE this instruction. + y.p; + } + + assertEq(i === 0 || called === 1, true); +} +for (var i = 0; i < 2; ++i) testMegamorphicLoadSlot(i); + +function testMegamorphicLoadSlotByValue(i) { + var xs = [ + {p: 0}, + {a: 0, p: 1}, + {a: 0, b: 0, p: 2}, + {a: 0, b: 0, c: 0, p: 3}, + {a: 0, b: 0, c: 0, d: 0, p: 4}, + {a: 0, b: 0, c: 0, d: 0, e: 0, p: 5}, + {a: 0, b: 0, c: 0, d: 0, e: 0, f: 0, p: 6}, + {a: 0, b: 0, c: 0, d: 0, e: 0, f: 0, g: 0, p: 7}, + {a: 0, b: 0, c: 0, d: 0, e: 0, f: 0, g: 0, h: 0, p: 8}, + {a: 0, b: 0, c: 0, d: 0, e: 0, f: 0, g: 0, h: 0, i: 0, p: 9}, + ]; + var called = 0; + var obj = { + get p() { + called++; + } + }; + + var p = "p"; + for (var j = 0; j <= 100; ++j) { + // Don't use if-statements to avoid cold code bailouts. + var x = xs[j % 10]; + var y = [x, obj][(i === 1 && j === 100)|0]; + + // Can't DCE this instruction. + y[p]; + } + + assertEq(i === 0 || called === 1, true); +} +for (var i = 0; i < 2; ++i) testMegamorphicLoadSlotByValue(i); + +function testMegamorphicHasProp(i) { + var xs = [ + {p: 0}, + {a: 0, p: 1}, + {a: 0, b: 0, p: 2}, + {a: 0, b: 0, c: 0, p: 3}, + {a: 0, b: 0, c: 0, d: 0, p: 4}, + {a: 0, b: 0, c: 0, d: 0, e: 0, p: 5}, + {a: 0, b: 0, c: 0, d: 0, e: 0, f: 0, p: 6}, + {a: 0, b: 0, c: 0, d: 0, e: 0, f: 0, g: 0, p: 7}, + {a: 0, b: 0, c: 0, d: 0, e: 0, f: 0, g: 0, h: 0, p: 8}, + {a: 0, b: 0, c: 0, d: 0, e: 0, f: 0, g: 0, h: 0, i: 0, p: 9}, + ]; + var called = 0; + var obj = new Proxy({}, { + has() { + called++; + } + }); + + for (var j = 0; j <= 100; ++j) { + // Don't use if-statements to avoid cold code bailouts. + var x = xs[j % 10]; + var y = [x, obj][(i === 1 && j === 100)|0]; + + // Can't DCE this instruction. + "p" in y; + } + + assertEq(i === 0 || called === 1, true); +} +for (var i = 0; i < 2; ++i) testMegamorphicHasProp(i); diff --git a/js/src/jit-test/tests/warp/min-max-foldsTo-1.js b/js/src/jit-test/tests/warp/min-max-foldsTo-1.js new file mode 100644 index 0000000000..accc6ea416 --- /dev/null +++ b/js/src/jit-test/tests/warp/min-max-foldsTo-1.js @@ -0,0 +1,47 @@ +with ({}); // Don't inline anything into the top-level script. + +function args() { return arguments; } + +for (let xs of [ + // Array + [[], [1, 2, 3]], + + // String + ["", "asdf"], + + // ArrayBufferView + [new Int32Array(0), new Int32Array(10)], + + // Arguments + [args(), args(1, 2, 3)], +]) { + for (let cst of [0, -1]) { + // Fold `Math.min(length ≥ 0, constant ≤ 0)` to `constant`. + let min = Function("x", `return Math.min(x.length, ${cst})`); + for (let i = 0; i < 100; ++i) { + let x = xs[i & 1]; + assertEq(min(x), cst); + } + + // Reverse operands. + min = Function("x", `return Math.min(${cst}, x.length)`); + for (let i = 0; i < 100; ++i) { + let x = xs[i & 1]; + assertEq(min(x), cst); + } + + // Fold `Math.max(length ≥ 0, constant ≤ 0)` to `length`. + let max = Function("x", `return Math.max(x.length, ${cst})`); + for (let i = 0; i < 100; ++i) { + let x = xs[i & 1]; + assertEq(max(x), x.length); + } + + // Reverse operands. + max = Function("x", `return Math.max(${cst}, x.length)`); + for (let i = 0; i < 100; ++i) { + let x = xs[i & 1]; + assertEq(max(x), x.length); + } + } +} diff --git a/js/src/jit-test/tests/warp/min-max-foldsTo-2.js b/js/src/jit-test/tests/warp/min-max-foldsTo-2.js new file mode 100644 index 0000000000..f848ba824c --- /dev/null +++ b/js/src/jit-test/tests/warp/min-max-foldsTo-2.js @@ -0,0 +1,153 @@ +with ({}); // Don't inline anything into the top-level script. + +// Fold min(x, min(y, z)) to min(min(x, y), z) with constant min(x, y). +for (let x of [-Infinity, -10, 0, 10, Infinity, NaN]) { + for (let y of [-Infinity, -10, 0, 10, Infinity, NaN]) { + for (let z of [-Infinity, -10, 0, 10, Infinity, NaN]) { + let fn = Function("z", `return Math.min(${x}, Math.min(${y}, z))`); + for (let i = 0; i < 100; ++i) { + let r = fn(z); + assertEq(r, Math.min(x, Math.min(y, z))); + assertEq(r, Math.min(Math.min(x, y), z)); + } + + // Reverse operands. + fn = Function("z", `return Math.min(${x}, Math.min(z, ${y}))`); + for (let i = 0; i < 100; ++i) { + let r = fn(z); + assertEq(r, Math.min(x, Math.min(z, y))); + assertEq(r, Math.min(Math.min(x, y), z)); + } + + // Reverse operands. + fn = Function("z", `return Math.min(Math.min(${y}, z), ${x})`); + for (let i = 0; i < 100; ++i) { + let r = fn(z); + assertEq(r, Math.min(Math.min(y, z), x)); + assertEq(r, Math.min(Math.min(x, y), z)); + } + + // Reverse operands. + fn = Function("z", `return Math.min(Math.min(z, ${y}), ${x})`); + for (let i = 0; i < 100; ++i) { + let r = fn(z); + assertEq(r, Math.min(Math.min(z, y), x)); + assertEq(r, Math.min(Math.min(x, y), z)); + } + } + } +} + +// Fold max(x, max(y, z)) to max(max(x, y), z) with constant max(x, y). +for (let x of [-Infinity, -10, 0, 10, Infinity, NaN]) { + for (let y of [-Infinity, -10, 0, 10, Infinity, NaN]) { + for (let z of [-Infinity, -10, 0, 10, Infinity, NaN]) { + let fn = Function("z", `return Math.max(${x}, Math.max(${y}, z))`); + for (let i = 0; i < 100; ++i) { + let r = fn(z); + assertEq(r, Math.max(x, Math.max(y, z))); + assertEq(r, Math.max(Math.max(x, y), z)); + } + + // Reverse operands. + fn = Function("z", `return Math.max(${x}, Math.max(z, ${y}))`); + for (let i = 0; i < 100; ++i) { + let r = fn(z); + assertEq(r, Math.max(x, Math.max(z, y))); + assertEq(r, Math.max(Math.max(x, y), z)); + } + + // Reverse operands. + fn = Function("z", `return Math.max(Math.max(${y}, z), ${x})`); + for (let i = 0; i < 100; ++i) { + let r = fn(z); + assertEq(r, Math.max(Math.max(y, z), x)); + assertEq(r, Math.max(Math.max(x, y), z)); + } + + // Reverse operands. + fn = Function("z", `return Math.max(Math.max(z, ${y}), ${x})`); + for (let i = 0; i < 100; ++i) { + let r = fn(z); + assertEq(r, Math.max(Math.max(z, y), x)); + assertEq(r, Math.max(Math.max(x, y), z)); + } + } + } +} + +// Fold min(x, max(y, z)) to max(min(x, y), min(x, z)). +for (let x of [-Infinity, -10, 0, 10, Infinity, NaN]) { + for (let y of [-Infinity, -10, 0, 10, Infinity, NaN]) { + for (let z of ["", "asdf", [], [1,2,3], new Int32Array(0), new Int32Array(10)]) { + let fn = Function("z", `return Math.min(${x}, Math.max(${y}, z.length))`); + for (let i = 0; i < 100; ++i) { + let r = fn(z); + assertEq(r, Math.min(x, Math.max(y, z.length))); + assertEq(r, Math.max(Math.min(x, y), Math.min(x, z.length))); + } + + // Reverse operands. + fn = Function("z", `return Math.min(${x}, Math.max(z.length, ${y}))`); + for (let i = 0; i < 100; ++i) { + let r = fn(z); + assertEq(r, Math.min(x, Math.max(z.length, y))); + assertEq(r, Math.max(Math.min(x, y), Math.min(x, z.length))); + } + + // Reverse operands. + fn = Function("z", `return Math.min(Math.max(${y}, z.length), ${x})`); + for (let i = 0; i < 100; ++i) { + let r = fn(z); + assertEq(r, Math.min(Math.max(y, z.length), x)); + assertEq(r, Math.max(Math.min(x, y), Math.min(x, z.length))); + } + + // Reverse operands. + fn = Function("z", `return Math.min(Math.max(z.length, ${y}), ${x})`); + for (let i = 0; i < 100; ++i) { + let r = fn(z); + assertEq(r, Math.min(Math.max(z.length, y), x)); + assertEq(r, Math.max(Math.min(x, y), Math.min(x, z.length))); + } + } + } +} + +// Fold max(x, min(y, z)) to min(max(x, y), max(x, z)). +for (let x of [-Infinity, -10, 0, 10, Infinity, NaN]) { + for (let y of [-Infinity, -10, 0, 10, Infinity, NaN]) { + for (let z of ["", "asdf", [], [1,2,3], new Int32Array(0), new Int32Array(10)]) { + let fn = Function("z", `return Math.max(${x}, Math.min(${y}, z.length))`); + for (let i = 0; i < 100; ++i) { + let r = fn(z); + assertEq(r, Math.max(x, Math.min(y, z.length))); + assertEq(r, Math.min(Math.max(x, y), Math.max(x, z.length))); + } + + // Reverse operands. + fn = Function("z", `return Math.max(${x}, Math.min(z.length, ${y}))`); + for (let i = 0; i < 100; ++i) { + let r = fn(z); + assertEq(r, Math.max(x, Math.min(z.length, y))); + assertEq(r, Math.min(Math.max(x, y), Math.max(x, z.length))); + } + + // Reverse operands. + fn = Function("z", `return Math.max(Math.min(${y}, z.length), ${x})`); + for (let i = 0; i < 100; ++i) { + let r = fn(z); + assertEq(r, Math.max(Math.min(y, z.length), x)); + assertEq(r, Math.min(Math.max(x, y), Math.max(x, z.length))); + } + + // Reverse operands. + fn = Function("z", `return Math.max(Math.min(z.length, ${y}), ${x})`); + for (let i = 0; i < 100; ++i) { + let r = fn(z); + assertEq(r, Math.max(Math.min(z.length, y), x)); + assertEq(r, Math.min(Math.max(x, y), Math.max(x, z.length))); + } + } + } +} diff --git a/js/src/jit-test/tests/warp/min-max-foldsTo-3.js b/js/src/jit-test/tests/warp/min-max-foldsTo-3.js new file mode 100644 index 0000000000..dd52ef2ffb --- /dev/null +++ b/js/src/jit-test/tests/warp/min-max-foldsTo-3.js @@ -0,0 +1,177 @@ +with ({}); // Don't inline anything into the top-level script. + +const numbers = [ + -Infinity, -10, -5, -2, -1, -0.5, 0, 0.5, 1, 2, 5, 10, Infinity, NaN, +]; + +// Test all supported types (Int32, Float32, Float64). +const converters = [ + x => `${x}`, + x => `(${x}|0)`, + x => `Math.fround(${x})`, + x => `numberToDouble(${x})`, +]; + +// Fold min(x, min(x, y)) to min(x, y). +for (let cvt of converters) { + let x = cvt("x"); + let y = cvt("y"); + let c = Function("a", `return ${cvt("a")}`); + + let min1 = Function("x, y", `return Math.min(${x}, Math.min(${x}, ${y}))`); + let min2 = Function("x, y", `return Math.min(${y}, Math.min(${x}, ${y}))`); + let min3 = Function("x, y", `return Math.min(Math.min(${x}, ${y}), ${x})`); + let min4 = Function("x, y", `return Math.min(Math.min(${x}, ${y}), ${y})`); + + for (let i = 0; i < 20; ++i) { + for (let j = 0; j < numbers.length; ++j) { + for (let k = 0; k < numbers.length; ++k) { + let x = numbers[j]; + let y = numbers[k] + let r1 = min1(x, y); + let r2 = min2(x, y); + let r3 = min3(x, y); + let r4 = min4(x, y); + + // Convert to the correct type before computing the expected results. + x = c(x); + y = c(y); + + assertEq(r1, Math.min(x, Math.min(x, y))); + assertEq(r1, Math.min(x, y)); + + assertEq(r2, Math.min(y, Math.min(x, y))); + assertEq(r2, Math.min(x, y)); + + assertEq(r3, Math.min(Math.min(x, y), x)); + assertEq(r3, Math.min(x, y)); + + assertEq(r4, Math.min(Math.min(x, y), y)); + assertEq(r4, Math.min(x, y)); + } + } + } +} + +// Fold max(x, max(x, y)) to max(x, y). +for (let cvt of converters) { + let x = cvt("x"); + let y = cvt("y"); + let c = Function("a", `return ${cvt("a")}`); + + let max1 = Function("x, y", `return Math.max(${x}, Math.max(${x}, ${y}))`); + let max2 = Function("x, y", `return Math.max(${y}, Math.max(${x}, ${y}))`); + let max3 = Function("x, y", `return Math.max(Math.max(${x}, ${y}), ${x})`); + let max4 = Function("x, y", `return Math.max(Math.max(${x}, ${y}), ${y})`); + + for (let i = 0; i < 20; ++i) { + for (let j = 0; j < numbers.length; ++j) { + for (let k = 0; k < numbers.length; ++k) { + let x = numbers[j]; + let y = numbers[k] + let r1 = max1(x, y); + let r2 = max2(x, y); + let r3 = max3(x, y); + let r4 = max4(x, y); + + // Convert to the correct type before computing the expected results. + x = c(x); + y = c(y); + + assertEq(r1, Math.max(x, Math.max(x, y))); + assertEq(r1, Math.max(x, y)); + + assertEq(r2, Math.max(y, Math.max(x, y))); + assertEq(r2, Math.max(x, y)); + + assertEq(r3, Math.max(Math.max(x, y), x)); + assertEq(r3, Math.max(x, y)); + + assertEq(r4, Math.max(Math.max(x, y), y)); + assertEq(r4, Math.max(x, y)); + } + } + } +} + +// Fold max(x, min(x, y)) = x. +for (let cvt of converters) { + let x = cvt("x"); + let y = cvt("y"); + let c = Function("a", `return ${cvt("a")}`); + + let maxmin1 = Function("x, y", `return Math.max(${x}, Math.min(${x}, ${y}))`); + let maxmin2 = Function("x, y", `return Math.max(${y}, Math.min(${x}, ${y}))`); + let maxmin3 = Function("x, y", `return Math.max(Math.min(${x}, ${y}), ${x})`); + let maxmin4 = Function("x, y", `return Math.max(Math.min(${x}, ${y}), ${y})`); + + for (let i = 0; i < 20; ++i) { + for (let j = 0; j < numbers.length; ++j) { + for (let k = 0; k < numbers.length; ++k) { + let x = numbers[j]; + let y = numbers[k] + let r1 = maxmin1(x, y); + let r2 = maxmin2(x, y); + let r3 = maxmin3(x, y); + let r4 = maxmin4(x, y); + + // Convert to the correct type before computing the expected results. + x = c(x); + y = c(y); + + assertEq(r1, Math.max(x, Math.min(x, y))); + assertEq(r1, Number.isNaN(y) ? NaN : x); + + assertEq(r2, Math.max(y, Math.min(x, y))); + assertEq(r2, Number.isNaN(x) ? NaN : y); + + assertEq(r3, Math.max(Math.min(x, y), x)); + assertEq(r3, Number.isNaN(y) ? NaN : x); + + assertEq(r4, Math.max(Math.min(x, y), y)); + assertEq(r4, Number.isNaN(x) ? NaN : y); + } + } + } +} + +// Fold min(x, max(x, y)) = x. +for (let cvt of converters) { + let x = cvt("x"); + let y = cvt("y"); + let c = Function("a", `return ${cvt("a")}`); + + let minmax1 = Function("x, y", `return Math.min(${x}, Math.max(${x}, ${y}))`); + let minmax2 = Function("x, y", `return Math.min(${y}, Math.max(${x}, ${y}))`); + let minmax3 = Function("x, y", `return Math.min(Math.max(${x}, ${y}), ${x})`); + let minmax4 = Function("x, y", `return Math.min(Math.max(${x}, ${y}), ${y})`); + + for (let i = 0; i < 20; ++i) { + for (let j = 0; j < numbers.length; ++j) { + for (let k = 0; k < numbers.length; ++k) { + let x = numbers[j]; + let y = numbers[k] + let r1 = minmax1(x, y); + let r2 = minmax2(x, y); + let r3 = minmax3(x, y); + let r4 = minmax4(x, y); + + // Convert to the correct type before computing the expected results. + x = c(x); + y = c(y); + + assertEq(r1, Math.min(x, Math.max(x, y))); + assertEq(r1, Number.isNaN(y) ? NaN : x); + + assertEq(r2, Math.min(y, Math.max(x, y))); + assertEq(r2, Number.isNaN(x) ? NaN : y); + + assertEq(r3, Math.min(Math.max(x, y), x)); + assertEq(r3, Number.isNaN(y) ? NaN : x); + + assertEq(r4, Math.min(Math.max(x, y), y)); + assertEq(r4, Number.isNaN(x) ? NaN : y); + } + } + } +} diff --git a/js/src/jit-test/tests/warp/min-max-foldsTo-4.js b/js/src/jit-test/tests/warp/min-max-foldsTo-4.js new file mode 100644 index 0000000000..aa7e09ba22 --- /dev/null +++ b/js/src/jit-test/tests/warp/min-max-foldsTo-4.js @@ -0,0 +1,11 @@ +with ({}); // Don't inline anything into the top-level script. + +function f(x) { + return Math.min(Math.max(x / x, x), x); +} + +for (var i = 0; i < 100; ++i) { + f(1); +} + +assertEq(f(0), NaN); diff --git a/js/src/jit-test/tests/warp/non-int32-array-length.js b/js/src/jit-test/tests/warp/non-int32-array-length.js new file mode 100644 index 0000000000..4213f1991c --- /dev/null +++ b/js/src/jit-test/tests/warp/non-int32-array-length.js @@ -0,0 +1,10 @@ +function f(arr, len) { + for (var i = 0; i < 2000; i++) { + assertEq(arr.length, len); + } +} +var arr = [0]; +f(arr, 1); + +arr.length = 0xffff_ffff; +f(arr, 0xffff_ffff); diff --git a/js/src/jit-test/tests/warp/null-not-zero-index.js b/js/src/jit-test/tests/warp/null-not-zero-index.js new file mode 100644 index 0000000000..b55b743b95 --- /dev/null +++ b/js/src/jit-test/tests/warp/null-not-zero-index.js @@ -0,0 +1,17 @@ +// |jit-test| --no-threads + +function f(index) { + var a = [123]; + return a[index] +} + +function g() { + for (var i = 0; i < 100; i++) { + // Make sure |null| is not treated like a |0| index. + assertEq(f(i > 90 ? null : 0), i > 90 ? undefined : 123) + } +} + +for (var j = 0; j < 10; j++) { + g(); +} diff --git a/js/src/jit-test/tests/warp/number-tostring-with-base-uppercase.js b/js/src/jit-test/tests/warp/number-tostring-with-base-uppercase.js new file mode 100644 index 0000000000..4dd6b182a8 --- /dev/null +++ b/js/src/jit-test/tests/warp/number-tostring-with-base-uppercase.js @@ -0,0 +1,92 @@ +// Copy of "warp/number-tostring-with-base.js" with `toUpperCase()` conversion +// after `Number.prototype.toString(base)`. + +function testConstantBaseFastPathTemplate(xs) { + assertEq(xs.length, $BASE * $BASE); + for (let j = 0; j < 200;) { + // The fast path can be used for all integers below |base * base|. + for (let i = 0; i < $BASE * $BASE; ++i, ++j) { + assertEq(i.toString($BASE).toUpperCase(), xs[i]); + } + } +} + +// Test when Number.prototype.toString is called with a constant base argument +// and the fast path for static strings can be used. +for (let base = 2; base <= 36; ++base) { + let fn = Function(`return ${testConstantBaseFastPathTemplate}`.replaceAll("$BASE", base))(); + let xs = Array.from({length: base * base}, (_, i) => i.toString(base).toUpperCase()); + for (let i = 0; i < 2; ++i) { + fn(xs); + } +} + +function testConstantBaseTemplate(xs) { + assertEq(xs.length, $BASE * $BASE * 2); + for (let j = 0; j < 200;) { + // The fast path can only be used for integers below |base * base|. + for (let i = 0; i < $BASE * $BASE * 2; ++i, ++j) { + assertEq(i.toString($BASE).toUpperCase(), xs[i]); + } + } +} + +// Test when Number.prototype.toString is called with a constant base argument +// and the fast path for static strings can't always be used. +for (let base = 2; base <= 36; ++base) { + let fn = Function(`return ${testConstantBaseTemplate}`.replaceAll("$BASE", base))(); + let xs = Array.from({length: base * base * 2}, (_, i) => i.toString(base).toUpperCase()); + for (let i = 0; i < 2; ++i) { + fn(xs); + } +} + +function testVariableBaseFastPathTemplate(xs, ys) { + assertEq(ys.length, 2); + assertEq(ys[0], ys[1]); + let base = ys[0]; + + assertEq(xs.length, base * base); + + for (let j = 0; j < 200;) { + // The fast path can be used for all integers below |base * base|. + for (let i = 0; i < base * base; ++i, ++j) { + assertEq(i.toString(ys[i & 1]).toUpperCase(), xs[i]); + } + } +} + +// Test when Number.prototype.toString is called with a non-constant base argument +// and the fast path for static strings can be used. +for (let base = 2; base <= 36; ++base) { + let fn = Function(`return ${testVariableBaseFastPathTemplate}`)(); + let xs = Array.from({length: base * base}, (_, i) => i.toString(base).toUpperCase()); + for (let i = 0; i < 2; ++i) { + fn(xs, [base, base]); + } +} + +function testVariableBaseTemplate(xs, ys) { + assertEq(ys.length, 2); + assertEq(ys[0], ys[1]); + let base = ys[0]; + + assertEq(xs.length, base * base * 2); + + for (let j = 0; j < 200;) { + // The fast path can only be used for integers below |base * base|. + for (let i = 0; i < base * base * 2; ++i, ++j) { + assertEq(i.toString(ys[i & 1]).toUpperCase(), xs[i]); + } + } +} + +// Test when Number.prototype.toString is called with a non-constant base argument +// and the fast path for static strings can't always be used. +for (let base = 2; base <= 36; ++base) { + let fn = Function(`return ${testVariableBaseTemplate}`)(); + let xs = Array.from({length: base * base * 2}, (_, i) => i.toString(base).toUpperCase()); + for (let i = 0; i < 2; ++i) { + fn(xs, [base, base]); + } +} diff --git a/js/src/jit-test/tests/warp/number-tostring-with-base.js b/js/src/jit-test/tests/warp/number-tostring-with-base.js new file mode 100644 index 0000000000..47842bec3f --- /dev/null +++ b/js/src/jit-test/tests/warp/number-tostring-with-base.js @@ -0,0 +1,89 @@ +function testConstantBaseFastPathTemplate(xs) { + assertEq(xs.length, $BASE * $BASE); + for (let j = 0; j < 200;) { + // The fast path can be used for all integers below |base * base|. + for (let i = 0; i < $BASE * $BASE; ++i, ++j) { + assertEq(i.toString($BASE), xs[i]); + } + } +} + +// Test when Number.prototype.toString is called with a constant base argument +// and the fast path for static strings can be used. +for (let base = 2; base <= 36; ++base) { + let fn = Function(`return ${testConstantBaseFastPathTemplate}`.replaceAll("$BASE", base))(); + let xs = Array.from({length: base * base}, (_, i) => i.toString(base)); + for (let i = 0; i < 2; ++i) { + fn(xs); + } +} + +function testConstantBaseTemplate(xs) { + assertEq(xs.length, $BASE * $BASE * 2); + for (let j = 0; j < 200;) { + // The fast path can only be used for integers below |base * base|. + for (let i = 0; i < $BASE * $BASE * 2; ++i, ++j) { + assertEq(i.toString($BASE), xs[i]); + } + } +} + +// Test when Number.prototype.toString is called with a constant base argument +// and the fast path for static strings can't always be used. +for (let base = 2; base <= 36; ++base) { + let fn = Function(`return ${testConstantBaseTemplate}`.replaceAll("$BASE", base))(); + let xs = Array.from({length: base * base * 2}, (_, i) => i.toString(base)); + for (let i = 0; i < 2; ++i) { + fn(xs); + } +} + +function testVariableBaseFastPathTemplate(xs, ys) { + assertEq(ys.length, 2); + assertEq(ys[0], ys[1]); + let base = ys[0]; + + assertEq(xs.length, base * base); + + for (let j = 0; j < 200;) { + // The fast path can be used for all integers below |base * base|. + for (let i = 0; i < base * base; ++i, ++j) { + assertEq(i.toString(ys[i & 1]), xs[i]); + } + } +} + +// Test when Number.prototype.toString is called with a non-constant base argument +// and the fast path for static strings can be used. +for (let base = 2; base <= 36; ++base) { + let fn = Function(`return ${testVariableBaseFastPathTemplate}`)(); + let xs = Array.from({length: base * base}, (_, i) => i.toString(base)); + for (let i = 0; i < 2; ++i) { + fn(xs, [base, base]); + } +} + +function testVariableBaseTemplate(xs, ys) { + assertEq(ys.length, 2); + assertEq(ys[0], ys[1]); + let base = ys[0]; + + assertEq(xs.length, base * base * 2); + + for (let j = 0; j < 200;) { + // The fast path can only be used for integers below |base * base|. + for (let i = 0; i < base * base * 2; ++i, ++j) { + assertEq(i.toString(ys[i & 1]), xs[i]); + } + } +} + +// Test when Number.prototype.toString is called with a non-constant base argument +// and the fast path for static strings can't always be used. +for (let base = 2; base <= 36; ++base) { + let fn = Function(`return ${testVariableBaseTemplate}`)(); + let xs = Array.from({length: base * base * 2}, (_, i) => i.toString(base)); + for (let i = 0; i < 2; ++i) { + fn(xs, [base, base]); + } +} diff --git a/js/src/jit-test/tests/warp/object-class-tostring.js b/js/src/jit-test/tests/warp/object-class-tostring.js new file mode 100644 index 0000000000..d26a2d95a5 --- /dev/null +++ b/js/src/jit-test/tests/warp/object-class-tostring.js @@ -0,0 +1,65 @@ +function testCongruent(i) { + var p = {}; + var o = { + // Add toString as an own property, so it'll be always found on this object, + // even when properties are changed on the prototype. + toString: Object.prototype.toString, + + // Add a custom prototype, so we can add @@toStringTag without modifying the + // shape of this object. + __proto__: p, + }; + var xs = [{}, p]; + var ys = ["[object Object]", "[object Test]"]; + + for (var j = 0; j <= 100; ++j) { + // Don't use if-statements to avoid cold code bailouts + var x = xs[(i === 1 && j === 100)|0]; + var y = ys[(i === 1 && j === 100)|0]; + + // |o.toString()| must be executed twice, because |x[Symbol.toStringTag]| may + // have modified |o|. + var r = o.toString(); + x[Symbol.toStringTag] = "Test"; + var e = o.toString(); + + assertEq(r, "[object Object]"); + assertEq(e, y); + } +} +for (var i = 0; i < 2; ++i) testCongruent(i); + +function testUnobserved(i) { + var p = {}; + var o = { + // Add toString as an own property, so it'll be always found on this object, + // even when properties are changed on the prototype. + toString: Object.prototype.toString, + + // Add a custom prototype, so we can add @@toStringTag without modifying the + // shape of this object. + __proto__: p, + }; + var xs = [{}, p]; + var ys = [false, true]; + + for (var j = 0; j <= 100; ++j) { + // Don't use if-statements to avoid cold code bailouts + var x = xs[(i === 1 && j === 100)|0]; + var y = ys[(i === 1 && j === 100)|0]; + + var executed = false; + Object.defineProperty(x, Symbol.toStringTag, { + configurable: true, + get() { + executed = true; + } + }); + + // |o.toString()| must be executed even when the result isn't observed. + o.toString(); + + assertEq(executed, y); + } +} +for (var i = 0; i < 2; ++i) testUnobserved(i); diff --git a/js/src/jit-test/tests/warp/phi-specialization.js b/js/src/jit-test/tests/warp/phi-specialization.js new file mode 100644 index 0000000000..a466a9eb9b --- /dev/null +++ b/js/src/jit-test/tests/warp/phi-specialization.js @@ -0,0 +1,42 @@ +// |jit-test| --fast-warmup + +var sum = 0; + +function foo(copy, shouldThrow) { + switch (copy) { + case 0: + var x = 0; + try { + if (shouldThrow) { throw 0;} + x = 1; + } catch { + x = "a"; + } + // We create a specialized phi for x here, which bails out. + for (var i = 0; i < 100; i++) { + sum += x; + } + break; + case 1: + var y = 0; + try { + if (shouldThrow) { throw 0;} + y = 1; + } catch { + y = "a"; + } + // We do not create a specialized phi the second time. + for (var i = 0; i < 100; i++) { + sum += y; + } + break; + } +} + +with ({}) {} +for (var i = 0; i < 2; i++) { + for (var j = 0; j < 50; j++) { + foo(i, false); + } + foo(i, true); +} diff --git a/js/src/jit-test/tests/warp/polymorphic-to-bool.js b/js/src/jit-test/tests/warp/polymorphic-to-bool.js new file mode 100644 index 0000000000..adbfe2d791 --- /dev/null +++ b/js/src/jit-test/tests/warp/polymorphic-to-bool.js @@ -0,0 +1,39 @@ +// |jit-test| --fast-warmup; --no-threads + +function runTest(src, seen, last) { + with ({}) {} + + // Make a fresh script. + var foo = eval(src); + + // Compile it with polymorphic types. + for (var j = 0; j < 100; j++) { + foo(seen[j % seen.length]); + } + + // Now test the type sorted last. + assertEq(foo(last), false); +} + +function runTests(src) { + // Each of these |seen| sets will cause the |last| type to be + // the last type tested in testValueTruthyKernel. + runTest(src, [1n, Symbol("a"), 1.5, "", {} ], 1); + runTest(src, [1n, Symbol("a"), 1.5, "", true ], {}); + runTest(src, [1n, Symbol("a"), 1.5, true, {} ], "a"); + runTest(src, [1n, Symbol("a"), true, "", {} ], 1.5); + runTest(src, [1n, true, 1.5, "", {} ], Symbol("a")); + runTest(src, [true, Symbol("a"), 1.5, "", {} ], 1n); +} + +// JumpIfFalse +runTests("(x) => { if (x) { return false; }}"); + +// And +runTests("(x) => x && false"); + +// Or +runTests("(x) => !(x || true)"); + +// Not +runTests("(x) => !x"); diff --git a/js/src/jit-test/tests/warp/property-add-shape.js b/js/src/jit-test/tests/warp/property-add-shape.js new file mode 100644 index 0000000000..88348200bf --- /dev/null +++ b/js/src/jit-test/tests/warp/property-add-shape.js @@ -0,0 +1,98 @@ +function simple() { + var o = {a: 1}; + o.b = 2; + + assertEq(o.a, 1); + assertEq(o.b, 2); +} + +function condition1(b) { + var o = {a: 1}; + + if (b) { + o.b = 2; + } + + o.c = 3; + + assertEq(o.a, 1); + if (b) { + assertEq(o.b, 2); + } else { + assertEq('b' in o, false); + } + assertEq(o.c, 3); +} + +function condition2(b) { + var o = {a: 1}; + + if (b) { + o.b = 2; + } else { + o.b = 3; + } + + o.c = 3; + + assertEq(o.a, 1); + assertEq(o.b, b ? 2 : 3); + assertEq(o.c, 3); +} + +function condition3(b) { + var o = {a: 1}; + + if (b) { + o.b = 2; + } else { + o.b = 2; + } + + o.c = 3; + + assertEq(o.a, 1); + assertEq(o.b, 2); + assertEq(o.c, 3); +} + +function condition4(b) { + var o = {a: 1}; + + o.bla = 2; + o.bla2 = 2; + o.bla3 = 2; + o.bla4 = 2; + + if (b) { + o.b = 2; + } else { + o.c = 2; + } + + o.d = 3; + + assertEq(o.a, 1); + if (b) { + assertEq(o.b, 2); + assertEq('c' in o, false); + } else { + assertEq('b' in o, false); + assertEq(o.c, 2); + } + assertEq(o.d, 3); +} + +function f() { + for (var i = 0; i < 10; i++) { + simple(); + condition1(i % 2 == 0) + condition2(i % 2 == 0) + condition3(i % 2 == 0) + condition4(i % 2 == 0) + } +} + +for (var i = 0; i < 10; i++) { + f(); +} diff --git a/js/src/jit-test/tests/warp/rest-elements.js b/js/src/jit-test/tests/warp/rest-elements.js new file mode 100644 index 0000000000..28525ad215 --- /dev/null +++ b/js/src/jit-test/tests/warp/rest-elements.js @@ -0,0 +1,40 @@ +// |jit-test| --ion-inlining=off; --fast-warmup +function calleeWithFormals(a, b, ...arr) { + assertEq(b, 2); + if (arr.length > 0) { + assertEq(arr[0], 3); + } + if (arr.length > 1) { + assertEq(arr[1], Math); + } + if (arr.length > 2) { + assertEq(arr[2], "foo"); + } + return arr; +} +function calleeWithoutFormals(...arr) { + if (arr.length > 0) { + assertEq(arr[0], 3); + } + if (arr.length > 1) { + assertEq(arr[1], Math); + } + if (arr.length > 2) { + assertEq(arr[2], "foo"); + } + return arr; +} +function f() { + for (var i = 0; i < 100; i++) { + assertEq(calleeWithFormals(1, 2).length, 0); + assertEq(calleeWithFormals(1, 2, 3).length, 1); + assertEq(calleeWithFormals(1, 2, 3, Math).length, 2); + assertEq(calleeWithFormals(1, 2, 3, Math, "foo").length, 3); + + assertEq(calleeWithoutFormals().length, 0); + assertEq(calleeWithoutFormals(3).length, 1); + assertEq(calleeWithoutFormals(3, Math).length, 2); + assertEq(calleeWithoutFormals(3, Math, "foo").length, 3); + } +} +f(); diff --git a/js/src/jit-test/tests/warp/scalar-replace-array-apply-array-01.js b/js/src/jit-test/tests/warp/scalar-replace-array-apply-array-01.js new file mode 100644 index 0000000000..0f88843615 --- /dev/null +++ b/js/src/jit-test/tests/warp/scalar-replace-array-apply-array-01.js @@ -0,0 +1,21 @@ +function escape(x) { with ({}) {} } + +function foo(...args) { + escape(args); + return bar.apply({}, args); +} + +// |foo| must be small enough to be inlinable. +assertEq(isSmallFunction(foo), true); + +function bar(x, y) { + return x + y; +} + +with ({}) {} + +var sum = 0; +for (var i = 0; i < 100; i++) { + sum += foo(1, 2); +} +assertEq(sum, 300); diff --git a/js/src/jit-test/tests/warp/scalar-replace-array-apply-array-02.js b/js/src/jit-test/tests/warp/scalar-replace-array-apply-array-02.js new file mode 100644 index 0000000000..e0689189ba --- /dev/null +++ b/js/src/jit-test/tests/warp/scalar-replace-array-apply-array-02.js @@ -0,0 +1,19 @@ +function foo(...args) { + args[0] = 3; + return bar.apply({}, args); +} + +// |foo| must be small enough to be inlinable. +assertEq(isSmallFunction(foo), true); + +function bar(x, y) { + return x + y; +} + +with ({}) {} + +var sum = 0; +for (var i = 0; i < 100; i++) { + sum += foo(1, 2); +} +assertEq(sum, 500); diff --git a/js/src/jit-test/tests/warp/scalar-replace-array-apply-array-03.js b/js/src/jit-test/tests/warp/scalar-replace-array-apply-array-03.js new file mode 100644 index 0000000000..54b88b1bdc --- /dev/null +++ b/js/src/jit-test/tests/warp/scalar-replace-array-apply-array-03.js @@ -0,0 +1,23 @@ +function escape() { with ({}) {} } + +function foo(i) { + return i; +} + +function bar(n, ...args) { + escape(args); + return foo.apply({}, args); +} + +// |bar| must be small enough to be inlinable. +assertEq(isSmallFunction(bar), true); + +function baz(a, n) { + return bar(n, n); +} + +var sum = 0; +for (var i = 0; i < 10000; i++) { + sum += baz(0, 1); +} +assertEq(sum, 10000); diff --git a/js/src/jit-test/tests/warp/scalar-replace-array-apply-array-04.js b/js/src/jit-test/tests/warp/scalar-replace-array-apply-array-04.js new file mode 100644 index 0000000000..e83b9946a3 --- /dev/null +++ b/js/src/jit-test/tests/warp/scalar-replace-array-apply-array-04.js @@ -0,0 +1,24 @@ +var result; + +function g(a, b) { + with ({}) {} + result = a + b; +} + +function escape() { with({}) {} } + +function f(...args) { + escape(args); + for (var i = 0; i < 50; ++i) { + g.apply(this, args); + } +} + +// |f| must be small enough to be inlinable. +assertEq(isSmallFunction(f), true); + +f(1, 2); +assertEq(result, 3); + +f(""); +assertEq(result, "undefined"); diff --git a/js/src/jit-test/tests/warp/scalar-replace-array-apply-array.js b/js/src/jit-test/tests/warp/scalar-replace-array-apply-array.js new file mode 100644 index 0000000000..7b8d12bd06 --- /dev/null +++ b/js/src/jit-test/tests/warp/scalar-replace-array-apply-array.js @@ -0,0 +1,43 @@ +// |jit-test| --fast-warmup; --no-threads + +setJitCompilerOption("baseline.warmup.trigger", 0); +setJitCompilerOption("ion.warmup.trigger", 100); + +// Prevent GC from cancelling/discarding Ion compilations. +gczeal(0); + +// Create a fresh set of functions for each argument count to avoid type pollution. +function makeTest(count) { + var args = Array(count).fill(0).join(","); + + return Function(` + function g() { + return arguments.length; + } + + function f(...args) { + // When |f| is inlined into its caller, the number of arguments is fixed and + // we can scalar replace the inlined rest array. + assertRecoveredOnBailout(args, true); + return g(...args); + } + + // |f| must be small enough to be inlined into the test function. + assertEq(isSmallFunction(f), true); + + function test() { + for (let i = 0; i < 1000; ++i) { + assertEq(f(${args}), ${count}); + } + } + + return test; + `)(); +} + +// Limited by gc::CanUseFixedElementsForArray(), see also WarpBuilder::build_Rest(). +const maxRestArgs = 14; + +for (let i = 0; i <= maxRestArgs; ++i) { + makeTest(i)(); +} diff --git a/js/src/jit-test/tests/warp/scalar-replace-array-construct-array-01.js b/js/src/jit-test/tests/warp/scalar-replace-array-construct-array-01.js new file mode 100644 index 0000000000..a68a9dad0e --- /dev/null +++ b/js/src/jit-test/tests/warp/scalar-replace-array-construct-array-01.js @@ -0,0 +1,28 @@ +setJitCompilerOption("inlining.bytecode-max-length", 200); + +function escape(x) { with ({}) {} } + +class Bar { + constructor(x, y) { + this.value = x + y; + } +} + +class Foo extends Bar { + constructor(...args) { + escape(args); + super(...args); + } +} + +// |Foo| must be small enough to be inlinable. +assertEq(isSmallFunction(Foo), true); + +with ({}) {} + +var sum = 0; +for (var i = 0; i < 100; i++) { + let obj = new Foo(1, 2); + sum += obj.value; +} +assertEq(sum, 300); diff --git a/js/src/jit-test/tests/warp/scalar-replace-array-construct-array-02.js b/js/src/jit-test/tests/warp/scalar-replace-array-construct-array-02.js new file mode 100644 index 0000000000..06d44779c1 --- /dev/null +++ b/js/src/jit-test/tests/warp/scalar-replace-array-construct-array-02.js @@ -0,0 +1,26 @@ +setJitCompilerOption("inlining.bytecode-max-length", 200); + +class Bar { + constructor(x, y) { + this.value = x + y; + } +} + +class Foo extends Bar { + constructor(...args) { + args[0] = 3; + super(...args); + } +} + +// |Foo| must be small enough to be inlinable. +assertEq(isSmallFunction(Foo), true); + +with ({}) {} + +var sum = 0; +for (var i = 0; i < 100; i++) { + let obj = new Foo(1, 2); + sum += obj.value; +} +assertEq(sum, 500); diff --git a/js/src/jit-test/tests/warp/scalar-replace-array-construct-array-03.js b/js/src/jit-test/tests/warp/scalar-replace-array-construct-array-03.js new file mode 100644 index 0000000000..72807b9f46 --- /dev/null +++ b/js/src/jit-test/tests/warp/scalar-replace-array-construct-array-03.js @@ -0,0 +1,32 @@ +setJitCompilerOption("inlining.bytecode-max-length", 200); + +function escape() { with ({}) {} } + +class Foo { + constructor(i) { + this.value = i; + } +} + +class Bar extends Foo { + constructor(n, ...args) { + escape(args); + super(...args); + } +} + +// |Bar| must be small enough to be inlinable. +assertEq(isSmallFunction(Bar), true); + +class Baz extends Bar { + constructor(a, n) { + super(n, n); + } +} + +var sum = 0; +for (var i = 0; i < 10000; i++) { + let obj = new Baz(0, 1); + sum += obj.value; +} +assertEq(sum, 10000); diff --git a/js/src/jit-test/tests/warp/scalar-replace-array-construct-array-04.js b/js/src/jit-test/tests/warp/scalar-replace-array-construct-array-04.js new file mode 100644 index 0000000000..290bbddcf7 --- /dev/null +++ b/js/src/jit-test/tests/warp/scalar-replace-array-construct-array-04.js @@ -0,0 +1,26 @@ +setJitCompilerOption("inlining.bytecode-max-length", 200); + +var result; + +function g(a, b) { + with ({}) {} + result = a + b; +} + +function escape() { with({}) {} } + +function f(...args) { + escape(args); + for (var i = 0; i < 50; ++i) { + new g(...args); + } +} + +// |f| must be small enough to be inlinable. +assertEq(isSmallFunction(f), true); + +f(1, 2); +assertEq(result, 3); + +f(""); +assertEq(result, "undefined"); diff --git a/js/src/jit-test/tests/warp/scalar-replace-array-construct-array.js b/js/src/jit-test/tests/warp/scalar-replace-array-construct-array.js new file mode 100644 index 0000000000..cd3b02c4e5 --- /dev/null +++ b/js/src/jit-test/tests/warp/scalar-replace-array-construct-array.js @@ -0,0 +1,48 @@ +// |jit-test| --fast-warmup; --no-threads + +setJitCompilerOption("baseline.warmup.trigger", 0); +setJitCompilerOption("ion.warmup.trigger", 100); + +// Prevent GC from cancelling/discarding Ion compilations. +gczeal(0); + +// Create a fresh set of functions for each argument count to avoid type pollution. +function makeTest(count) { + var args = Array(count).fill(0).join(","); + + return Function(` + class Base { + constructor() { + this.count = arguments.length; + } + } + + class C extends Base { + constructor(...args) { + // When |C| is inlined into its caller, the number of arguments is fixed and + // we can scalar replace the inlined rest array. + assertRecoveredOnBailout(args, true); + return super(...args); + } + } + + // |C| must be small enough to be inlined into the test function. + assertEq(isSmallFunction(C), true); + + function test() { + for (let i = 0; i < 1000; ++i) { + let obj = new C(${args}); + assertEq(obj.count, ${count}); + } + } + + return test; + `)(); +} + +// Limited by gc::CanUseFixedElementsForArray(), see also WarpBuilder::build_Rest(). +const maxRestArgs = 14; + +for (let i = 0; i <= maxRestArgs; ++i) { + makeTest(i)(); +} diff --git a/js/src/jit-test/tests/warp/scalar-replace-array-iterator-next.js b/js/src/jit-test/tests/warp/scalar-replace-array-iterator-next.js new file mode 100644 index 0000000000..d523bdb896 --- /dev/null +++ b/js/src/jit-test/tests/warp/scalar-replace-array-iterator-next.js @@ -0,0 +1,24 @@ +//|jit-test| --ion-pruning=on + +// Verify that we can inline ArrayIteratorNext, +// and scalar-replace the result object. + +if (getJitCompilerOptions()["ion.warmup.trigger"] <= 150) + setJitCompilerOption("ion.warmup.trigger", 150); + +gczeal(0); + +function foo(arr) { + var iterator = arr[Symbol.iterator](); + while (true) { + var result = iterator.next(); + trialInline(); + assertRecoveredOnBailout(result, true); + if (result.done) { + break; + } + } +} + +with ({}) {} +foo(Array(1000)); diff --git a/js/src/jit-test/tests/warp/scalar-replace-rest-apply-array.js b/js/src/jit-test/tests/warp/scalar-replace-rest-apply-array.js new file mode 100644 index 0000000000..315dd58b5d --- /dev/null +++ b/js/src/jit-test/tests/warp/scalar-replace-rest-apply-array.js @@ -0,0 +1,38 @@ +// Prevent GC from cancelling/discarding Ion compilations. +gczeal(0); + +// Create a fresh set of functions for each argument count to avoid type pollution. +function makeTest(count) { + var args = Array(count).fill(0).join(","); + + return Function(` + function g() { + return arguments.length; + } + + function f(...args) { + assertRecoveredOnBailout(args, true); + return g(...args); + } + + function test() { + // Ensure |f| isn't inlined into its caller. + with ({}); + + for (let i = 0; i < 1000; ++i) { + assertEq(f(${args}), ${count}); + } + } + + return test; + `)(); +} + +// Inline rest arguments are limited to 14 elements, cf. +// gc::CanUseFixedElementsForArray() in WarpBuilder::build_Rest(). +// There isn't such limit when the function isn't inlined. +const maxRestArgs = 20; + +for (let i = 0; i <= maxRestArgs; ++i) { + makeTest(i)(); +} diff --git a/js/src/jit-test/tests/warp/scalar-replace-rest-construct-array.js b/js/src/jit-test/tests/warp/scalar-replace-rest-construct-array.js new file mode 100644 index 0000000000..e2c0c5b354 --- /dev/null +++ b/js/src/jit-test/tests/warp/scalar-replace-rest-construct-array.js @@ -0,0 +1,43 @@ +// Prevent GC from cancelling/discarding Ion compilations. +gczeal(0); + +// Create a fresh set of functions for each argument count to avoid type pollution. +function makeTest(count) { + var args = Array(count).fill(0).join(","); + + return Function(` + class Base { + constructor() { + this.count = arguments.length; + } + } + + class C extends Base { + constructor(...args) { + assertRecoveredOnBailout(args, true); + return super(...args); + } + } + + function test() { + // Ensure |C| isn't inlined into its caller. + with ({}); + + for (let i = 0; i < 1000; ++i) { + let obj = new C(${args}); + assertEq(obj.count, ${count}); + } + } + + return test; + `)(); +} + +// Inline rest arguments are limited to 14 elements, cf. +// gc::CanUseFixedElementsForArray() in WarpBuilder::build_Rest(). +// There isn't such limit when the function isn't inlined. +const maxRestArgs = 20; + +for (let i = 0; i <= maxRestArgs; ++i) { + makeTest(i)(); +} diff --git a/js/src/jit-test/tests/warp/set-has-bigint.js b/js/src/jit-test/tests/warp/set-has-bigint.js new file mode 100644 index 0000000000..f282760eb2 --- /dev/null +++ b/js/src/jit-test/tests/warp/set-has-bigint.js @@ -0,0 +1,192 @@ +// Similar test as "cacheir/set-has-bigint.js", except that we now perform +// duplicate lookups to ensure GVN works properly. + +// Return a new set, possibly filling some dummy entries to enforce creating +// multiple hash buckets. +function createSet(values, n) { + var xs = [...values]; + for (var i = 0; i < n; ++i) { + xs.push({}); + } + return new Set(xs); +} + +function runTest(fn) { + fn(0); + fn(100); +} + +function testInlineDigitsSameSign_same_set(n) { + var xs = [1n, 2n]; + var ys = [3n, 4n]; + var zs = [...xs, ...ys]; + var set = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set.has(z)) c++; + if (set.has(z)) c++; + } + assertEq(c, N); +} +runTest(testInlineDigitsSameSign_same_set); + +function testInlineDigitsDifferentSign_same_set(n) { + var xs = [-1n, 2n]; + var ys = [1n, -2n]; + var zs = [...xs, ...ys]; + var set = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set.has(z)) c++; + if (set.has(z)) c++; + } + assertEq(c, N); +} +runTest(testInlineDigitsDifferentSign_same_set); + +function testHeapDigitsSameSign_same_set(n) { + // Definitely uses heap digits. + var heap = 2n ** 1000n; + + var xs = [heap + 1n, heap + 2n]; + var ys = [heap + 3n, heap + 4n]; + var zs = [...xs, ...ys]; + var set = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set.has(z)) c++; + if (set.has(z)) c++; + } + assertEq(c, N); +} +runTest(testHeapDigitsSameSign_same_set); + +function testHeapDigitsDifferentSign_same_set(n) { + // Definitely uses heap digits. + var heap = 2n ** 1000n; + + var xs = [-(heap + 1n), heap + 2n]; + var ys = [heap + 1n, -(heap + 2n)]; + var zs = [...xs, ...ys]; + var set = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set.has(z)) c++; + if (set.has(z)) c++; + } + assertEq(c, N); +} +runTest(testHeapDigitsDifferentSign_same_set); + +// Duplicate the above tests, but this time use a different set. + +function testInlineDigitsSameSign_different_set(n) { + var xs = [1n, 2n]; + var ys = [3n, 4n]; + var zs = [...xs, ...ys]; + var set1 = createSet(xs, n); + var set2 = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set1.has(z)) c++; + if (set2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testInlineDigitsSameSign_different_set); + +function testInlineDigitsDifferentSign_different_set(n) { + var xs = [-1n, 2n]; + var ys = [1n, -2n]; + var zs = [...xs, ...ys]; + var set1 = createSet(xs, n); + var set2 = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set1.has(z)) c++; + if (set2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testInlineDigitsDifferentSign_different_set); + +function testHeapDigitsSameSign_different_set(n) { + // Definitely uses heap digits. + var heap = 2n ** 1000n; + + var xs = [heap + 1n, heap + 2n]; + var ys = [heap + 3n, heap + 4n]; + var zs = [...xs, ...ys]; + var set1 = createSet(xs, n); + var set2 = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set1.has(z)) c++; + if (set2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testHeapDigitsSameSign_different_set); + +function testHeapDigitsDifferentSign_different_set(n) { + // Definitely uses heap digits. + var heap = 2n ** 1000n; + + var xs = [-(heap + 1n), heap + 2n]; + var ys = [heap + 1n, -(heap + 2n)]; + var zs = [...xs, ...ys]; + var set1 = createSet(xs, n); + var set2 = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set1.has(z)) c++; + if (set2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testHeapDigitsDifferentSign_different_set); + +// Test the alias information is correct. + +function test_alias(n) { + var xs = [1n, 2n]; + var set = createSet([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + set.add(x); + if (set.has(x)) c++; + + set.delete(x); + if (set.has(x)) c++; + } + assertEq(c, N); +} +runTest(test_alias); diff --git a/js/src/jit-test/tests/warp/set-has-nongcthing.js b/js/src/jit-test/tests/warp/set-has-nongcthing.js new file mode 100644 index 0000000000..845b9431b2 --- /dev/null +++ b/js/src/jit-test/tests/warp/set-has-nongcthing.js @@ -0,0 +1,307 @@ +// Similar test as "cacheir/set-has-nongcthing.js", except that we now perform +// duplicate lookups to ensure GVN works properly. + +// Return a new set, possibly filling some dummy entries to enforce creating +// multiple hash buckets. +function createSet(values, n) { + var xs = [...values]; + for (var i = 0; i < n; ++i) { + xs.push({}); + } + return new Set(xs); +} + +function runTest(fn) { + fn(0); + fn(100); +} + +function testInt32_same_set(n) { + var xs = [1, 2]; + var ys = [3, 4]; + var zs = [...xs, ...ys]; + var set = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set.has(z)) c++; + if (set.has(z)) c++; + } + assertEq(c, N); +} +runTest(testInt32_same_set); + +function testDouble_same_set(n) { + var xs = [Math.PI, Infinity]; + var ys = [Math.E, -Infinity]; + var zs = [...xs, ...ys]; + var set = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set.has(z)) c++; + if (set.has(z)) c++; + } + assertEq(c, N); +} +runTest(testDouble_same_set); + +function testZero_same_set(n) { + var xs = [0, -0]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var set = createSet([0], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set.has(z)) c++; + if (set.has(z)) c++; + } + assertEq(c, N); +} +runTest(testZero_same_set); + +function testNaN_same_set(n) { + var xs = [NaN, -NaN]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var set = createSet([NaN], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set.has(z)) c++; + if (set.has(z)) c++; + } + assertEq(c, N); +} +runTest(testNaN_same_set); + +function testUndefinedAndNull_same_set(n) { + var xs = [undefined, null]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var set = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set.has(z)) c++; + if (set.has(z)) c++; + } + assertEq(c, N); +} +runTest(testUndefinedAndNull_same_set); + +function testBoolean_same_set(n) { + var xs = [true, false]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var set = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set.has(z)) c++; + if (set.has(z)) c++; + } + assertEq(c, N); +} +runTest(testBoolean_same_set); + +// Duplicate the above tests, but this time use a different set. + +function testInt32_different_set(n) { + var xs = [1, 2]; + var ys = [3, 4]; + var zs = [...xs, ...ys]; + var set1 = createSet(xs, n); + var set2 = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set1.has(z)) c++; + if (set2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testInt32_different_set); + +function testDouble_different_set(n) { + var xs = [Math.PI, Infinity]; + var ys = [Math.E, -Infinity]; + var zs = [...xs, ...ys]; + var set1 = createSet(xs, n); + var set2 = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set1.has(z)) c++; + if (set2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testDouble_different_set); + +function testZero_different_set(n) { + var xs = [0, -0]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var set1 = createSet([0], n); + var set2 = createSet([0], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set1.has(z)) c++; + if (set2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testZero_different_set); + +function testNaN_different_set(n) { + var xs = [NaN, -NaN]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var set1 = createSet([NaN], n); + var set2 = createSet([NaN], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set1.has(z)) c++; + if (set2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testNaN_different_set); + +function testUndefinedAndNull_different_set(n) { + var xs = [undefined, null]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var set1 = createSet(xs, n); + var set2 = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set1.has(z)) c++; + if (set2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testUndefinedAndNull_different_set); + +function testBoolean_different_set(n) { + var xs = [true, false]; + var ys = [1, -1]; + var zs = [...xs, ...ys]; + var set1 = createSet(xs, n); + var set2 = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set1.has(z)) c++; + if (set2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testBoolean_different_set); + +// Test the alias information is correct. + +function testInt32_alias(n) { + var xs = [1, 2]; + var set = createSet([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + set.add(x); + if (set.has(x)) c++; + + set.delete(x); + if (set.has(x)) c++; + } + assertEq(c, N); +} +runTest(testInt32_alias); + +function testDouble_alias(n) { + var xs = [Math.PI, Infinity]; + var set = createSet([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + set.add(x); + if (set.has(x)) c++; + + set.delete(x); + if (set.has(x)) c++; + } + assertEq(c, N); +} +runTest(testDouble_alias); + +function testUndefinedAndNull_alias(n) { + var xs = [undefined, null]; + var set = createSet([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + set.add(x); + if (set.has(x)) c++; + + set.delete(x); + if (set.has(x)) c++; + } + assertEq(c, N); +} +runTest(testUndefinedAndNull_alias); + +function testBoolean_alias(n) { + var xs = [true, false]; + var set = createSet([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + set.add(x); + if (set.has(x)) c++; + + set.delete(x); + if (set.has(x)) c++; + } + assertEq(c, N); +} +runTest(testBoolean_alias); diff --git a/js/src/jit-test/tests/warp/set-has-object.js b/js/src/jit-test/tests/warp/set-has-object.js new file mode 100644 index 0000000000..87cf85f791 --- /dev/null +++ b/js/src/jit-test/tests/warp/set-has-object.js @@ -0,0 +1,97 @@ +// Similar test as "cacheir/set-has-object.js", except that we now perform +// duplicate lookups to ensure GVN works properly. + +// Return a new set, possibly filling some dummy entries to enforce creating +// multiple hash buckets. +function createSet(values, n) { + var xs = [...values]; + for (var i = 0; i < n; ++i) { + xs.push({}); + } + return new Set(xs); +} + +function runTest(fn) { + fn(0); + fn(100); +} + +function test_same_set(n) { + var xs = [{}, {}]; + var ys = [{}, {}]; + var zs = [...xs, ...ys]; + var set = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set.has(z)) c++; + if (set.has(z)) c++; + } + assertEq(c, N); +} +runTest(test_same_set); + +// Duplicate the above tests, but this time use a different set. + +function test_different_set(n) { + var xs = [{}, {}]; + var ys = [{}, {}]; + var zs = [...xs, ...ys]; + var set1 = createSet(xs, n); + var set2 = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set1.has(z)) c++; + if (set2.has(z)) c++; + } + assertEq(c, N); +} +runTest(test_different_set); + +// Test the alias information is correct. + +function test_alias(n) { + var xs = [{}, {}]; + var set = createSet([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + set.add(x); + if (set.has(x)) c++; + + set.delete(x); + if (set.has(x)) c++; + } + assertEq(c, N); +} +runTest(test_alias); + +// And finally test that we don't actually support GVN for objects, because the +// hash changes when moving an object. + +function testRekey() { + var set = new Set(); + var c = 0; + var N = 100; + for (var i = 0; i < N; ++i) { + var k = {}; + set.add(k); + + if (set.has(k)) c++; + + minorgc(); + + if (set.has(k)) c++; + } + + assertEq(c, N * 2); +} +testRekey(); diff --git a/js/src/jit-test/tests/warp/set-has-string.js b/js/src/jit-test/tests/warp/set-has-string.js new file mode 100644 index 0000000000..65442a4249 --- /dev/null +++ b/js/src/jit-test/tests/warp/set-has-string.js @@ -0,0 +1,147 @@ +// Similar test as "cacheir/set-has-string.js", except that we now perform +// duplicate lookups to ensure GVN works properly. + +// Return a new set, possibly filling some dummy entries to enforce creating +// multiple hash buckets. +function createSet(values, n) { + var xs = [...values]; + for (var i = 0; i < n; ++i) { + xs.push({}); + } + return new Set(xs); +} + +function runTest(fn) { + fn(0); + fn(100); +} + +function testConstant_same_set(n) { + var xs = ["a", "b"]; + var ys = ["c", "d"]; + var zs = [...xs, ...ys]; + var set = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set.has(z)) c++; + if (set.has(z)) c++; + } + assertEq(c, N); +} +runTest(testConstant_same_set); + +function testComputed_same_set(n) { + var xs = ["a", "b"]; + var ys = ["c", "d"]; + var zs = [...xs, ...ys]; + var set = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + z = String.fromCharCode(z.charCodeAt(0)); + if (set.has(z)) c++; + if (set.has(z)) c++; + } + assertEq(c, N); +} +runTest(testComputed_same_set); + +function testRope_same_set(n) { + var xs = ["a", "b"]; + var ys = ["c", "d"]; + var zs = [...xs, ...ys]; + var set = createSet(xs.map(x => x.repeat(100)), n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3].repeat(100); + if (set.has(z)) c++; + if (set.has(z)) c++; + } + assertEq(c, N); +} +runTest(testRope_same_set); + +// Duplicate the above tests, but this time use a different set. + +function testConstant_different_set(n) { + var xs = ["a", "b"]; + var ys = ["c", "d"]; + var zs = [...xs, ...ys]; + var set1 = createSet(xs, n); + var set2 = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set1.has(z)) c++; + if (set2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testConstant_different_set); + +function testComputed_different_set(n) { + var xs = ["a", "b"]; + var ys = ["c", "d"]; + var zs = [...xs, ...ys]; + var set1 = createSet(xs, n); + var set2 = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + z = String.fromCharCode(z.charCodeAt(0)); + if (set1.has(z)) c++; + if (set2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testComputed_different_set); + +function testRope_different_set(n) { + var xs = ["a", "b"]; + var ys = ["c", "d"]; + var zs = [...xs, ...ys]; + var set1 = createSet(xs.map(x => x.repeat(100)), n); + var set2 = createSet(xs.map(x => x.repeat(100)), n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3].repeat(100); + if (set1.has(z)) c++; + if (set2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testRope_different_set); + +// Test the alias information is correct. + +function test_alias(n) { + var xs = ["a", "b"]; + var set = createSet([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + set.add(x); + if (set.has(x)) c++; + + set.delete(x); + if (set.has(x)) c++; + } + assertEq(c, N); +} +runTest(test_alias); diff --git a/js/src/jit-test/tests/warp/set-has-symbol.js b/js/src/jit-test/tests/warp/set-has-symbol.js new file mode 100644 index 0000000000..0f1c697ca3 --- /dev/null +++ b/js/src/jit-test/tests/warp/set-has-symbol.js @@ -0,0 +1,75 @@ +// Similar test as "cacheir/set-has-symbol.js", except that we now perform +// duplicate lookups to ensure GVN works properly. + +// Return a new set, possibly filling some dummy entries to enforce creating +// multiple hash buckets. +function createSet(values, n) { + var xs = [...values]; + for (var i = 0; i < n; ++i) { + xs.push({}); + } + return new Set(xs); +} + +function runTest(fn) { + fn(0); + fn(100); +} + +function test_same_set(n) { + var xs = [Symbol(), Symbol()]; + var ys = [Symbol(), Symbol()]; + var zs = [...xs, ...ys]; + var set = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set.has(z)) c++; + if (set.has(z)) c++; + } + assertEq(c, N); +} +runTest(test_same_set); + +// Duplicate the above tests, but this time use a different set. + +function test_different_set(n) { + var xs = [Symbol(), Symbol()]; + var ys = [Symbol(), Symbol()]; + var zs = [...xs, ...ys]; + var set1 = createSet(xs, n); + var set2 = createSet(xs, n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 3]; + if (set1.has(z)) c++; + if (set2.has(z)) c++; + } + assertEq(c, N); +} +runTest(test_different_set); + +// Test the alias information is correct. + +function test_alias(n) { + var xs = [Symbol(), Symbol()]; + var set = createSet([], n); + + var N = 100; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 1]; + + set.add(x); + if (set.has(x)) c++; + + set.delete(x); + if (set.has(x)) c++; + } + assertEq(c, N); +} +runTest(test_alias); diff --git a/js/src/jit-test/tests/warp/set-has-value.js b/js/src/jit-test/tests/warp/set-has-value.js new file mode 100644 index 0000000000..4a6a8464e0 --- /dev/null +++ b/js/src/jit-test/tests/warp/set-has-value.js @@ -0,0 +1,97 @@ +// Similar test as "cacheir/set-has-value.js", except that we now perform +// duplicate lookups to ensure GVN works properly. + +// Return a new set, possibly filling some dummy entries to enforce creating +// multiple hash buckets. +function createSet(values, n) { + var xs = [...values]; + for (var i = 0; i < n; ++i) { + xs.push({}); + } + return new Set(xs); +} + +function runTest(fn) { + fn(0); + fn(100); +} + +function testPolymorphic_same_set(n) { + var xs = [10, 10.5, "test", Symbol("?"), 123n, -123n, {}, []]; + var ys = [-0, NaN, "bad", Symbol("!"), 42n, -99n, {}, []]; + var zs = [...xs, ...ys]; + var set = createSet(xs, n); + + var N = 128; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 15]; + if (set.has(z)) c++; + if (set.has(z)) c++; + } + assertEq(c, N); +} +runTest(testPolymorphic_same_set); + +// Duplicate the above tests, but this time use a different set. + +function testPolymorphic_different_set(n) { + var xs = [10, 10.5, "test", Symbol("?"), 123n, -123n, {}, []]; + var ys = [-0, NaN, "bad", Symbol("!"), 42n, -99n, {}, []]; + var zs = [...xs, ...ys]; + var set1 = createSet(xs, n); + var set2 = createSet(xs, n); + + var N = 128; + var c = 0; + for (var i = 0; i < N; ++i) { + var z = zs[i & 15]; + if (set1.has(z)) c++; + if (set2.has(z)) c++; + } + assertEq(c, N); +} +runTest(testPolymorphic_different_set); + +// Test the alias information is correct. + +function testPolymorphic_alias(n) { + var xs = [10, 10.5, "test", Symbol("?"), 123n, -123n, {}, []]; + var set = createSet([], n); + + var N = 128; + var c = 0; + for (var i = 0; i < N; ++i) { + var x = xs[i & 15]; + + set.add(x); + if (set.has(x)) c++; + + set.delete(x); + if (set.has(x)) c++; + } + assertEq(c, N); +} +runTest(testPolymorphic_alias); + +// And finally test that we don't actually support GVN for values, because the +// hash changes when moving a value which holds an object. + +function testRekey() { + var set = new Set(); + var c = 0; + var N = 100; + for (var i = 0; i < N; ++i) { + var k = (i & 1) ? {} : null; + set.add(k); + + if (set.has(k)) c++; + + minorgc(); + + if (set.has(k)) c++; + } + + assertEq(c, N * 2); +} +testRekey(); diff --git a/js/src/jit-test/tests/warp/setelem-inlining-bailout.js b/js/src/jit-test/tests/warp/setelem-inlining-bailout.js new file mode 100644 index 0000000000..102b2d7dcc --- /dev/null +++ b/js/src/jit-test/tests/warp/setelem-inlining-bailout.js @@ -0,0 +1,26 @@ +// |jit-test| --fast-warmup; --no-threads + +// Inlining setters for SetElem ops will require bailout changes. + +with ({}) { } +var trigger = false; + +var obj = { + set f(x) { + if (trigger) { + bailout(); + } + } +}; + +var sum = 0; +function foo(x) { + for (var i = 0; i < 35; i++) { + var t = obj[x] = i; + sum += t; + trigger = i % 10 == 0; + } +} + +foo("f"); +assertEq(sum, 595); diff --git a/js/src/jit-test/tests/warp/small-inlinable-builtins.js b/js/src/jit-test/tests/warp/small-inlinable-builtins.js new file mode 100644 index 0000000000..d346894f6a --- /dev/null +++ b/js/src/jit-test/tests/warp/small-inlinable-builtins.js @@ -0,0 +1,10 @@ +// Ensure certain self-hosted built-in functions are small enough to be inlinable. + +assertEq(isSmallFunction(isFinite), true); +assertEq(isSmallFunction(isNaN), true); + +assertEq(isSmallFunction(Number.isFinite), true); +assertEq(isSmallFunction(Number.isNaN), true); + +assertEq(isSmallFunction(Number.isInteger), true); +assertEq(isSmallFunction(Number.isSafeInteger), true); diff --git a/js/src/jit-test/tests/warp/store-element-hole-negative-index.js b/js/src/jit-test/tests/warp/store-element-hole-negative-index.js new file mode 100644 index 0000000000..43d017769c --- /dev/null +++ b/js/src/jit-test/tests/warp/store-element-hole-negative-index.js @@ -0,0 +1,24 @@ +function test() { + Object.defineProperty(Array.prototype, -1, { + set() { + throw new Error("shouldn't get here"); + } + }); + + // A bunch of indices which grow the array. + var indices = []; + for (var i = 0; i < 10_000; ++i) indices.push(i); + + // And finally a negative index. + indices.push(-1); + + // Plain data properties use DefineDataProperty. + var desc = {value: 0, writable: true, enumerable: true, configurable: true}; + + var a = []; + for (var i = 0; i < indices.length; ++i) { + Object.defineProperty(a, indices[i], desc); + } +} + +test(); diff --git a/js/src/jit-test/tests/warp/store-element-hole-sparse-element.js b/js/src/jit-test/tests/warp/store-element-hole-sparse-element.js new file mode 100644 index 0000000000..2012404f51 --- /dev/null +++ b/js/src/jit-test/tests/warp/store-element-hole-sparse-element.js @@ -0,0 +1,24 @@ +function test() { + Object.defineProperty(Array.prototype, 10_000 * 50, { + set() { + throw new Error("shouldn't get here"); + } + }); + + // A bunch of indices which grow the array. + var indices = []; + for (var i = 0; i < 10_000; ++i) indices.push(i); + + // And finally an index for a sparse property. + indices.push(10_000 * 50); + + // Plain data properties use DefineDataProperty. + var desc = {value: 0, writable: true, enumerable: true, configurable: true}; + + var a = []; + for (var i = 0; i < indices.length; ++i) { + Object.defineProperty(a, indices[i], desc); + } +} + +test(); diff --git a/js/src/jit-test/tests/warp/string-char.js b/js/src/jit-test/tests/warp/string-char.js new file mode 100644 index 0000000000..fc41e5079a --- /dev/null +++ b/js/src/jit-test/tests/warp/string-char.js @@ -0,0 +1,15 @@ +function f(x) { + assertEq(x.charCodeAt(1), 0x62); + assertEq(x.charAt(1), "b"); + assertEq(x[1], "b"); +} + +function g() { + for (var i = 0; i < 100; i++) { + f("abc"); + } +} + +for (var j = 0; j < 10; j++) { + g(); +} diff --git a/js/src/jit-test/tests/warp/string-charCodeAt-constant-index-in-left-rope-child.js b/js/src/jit-test/tests/warp/string-charCodeAt-constant-index-in-left-rope-child.js new file mode 100644 index 0000000000..5835b019ad --- /dev/null +++ b/js/src/jit-test/tests/warp/string-charCodeAt-constant-index-in-left-rope-child.js @@ -0,0 +1,18 @@ +// `str.charCodeAt(0)` doesn't need to inspect the right rope child, because +// the first character is guaranteed to be in the left child. + +const ropes = [ + newRope("ABCDEFGHIJKL", "MNOPQRSTUVWXYZ"), + newRope("abcdefghijkl", "mnopqrstuvwxyz"), + + newRope("A", "BCDEFGHIJKLMNOPQRSTUVWXYZ"), + newRope("a", "bcdefghijklmnopqrstuvwxyz"), +]; + +for (let i = 0; i < 500; ++i) { + let rope = ropes[i & 3]; + + let actual = rope.charCodeAt(0); + let expected = 0x41 + (i & 1) * 0x20; + assertEq(actual, expected); +} diff --git a/js/src/jit-test/tests/warp/string-compare-char-in-bounds.js b/js/src/jit-test/tests/warp/string-compare-char-in-bounds.js new file mode 100644 index 0000000000..31c48a43fd --- /dev/null +++ b/js/src/jit-test/tests/warp/string-compare-char-in-bounds.js @@ -0,0 +1,24 @@ +// |str.char(idx) == "b"| is compiled as |str.charCodeAt(idx) == 0x62|. + +const strings = [ + "a", "b", "c", + "a-", "b-", "c-", +]; + +for (let i = 0; i < 1000; ++i) { + let str = strings[i % strings.length]; + + for (let j = 0; j < str.length; ++j) { + let ch = str.charAt(j); + let code = str.charCodeAt(j); + + assertEq(ch == "b", code == 0x62); + assertEq(ch != "b", code != 0x62); + + assertEq(ch < "b", code < 0x62); + assertEq(ch <= "b", code <= 0x62); + + assertEq(ch > "b", code > 0x62); + assertEq(ch >= "b", code >= 0x62); + } +} diff --git a/js/src/jit-test/tests/warp/string-compare-char-out-of-bounds.js b/js/src/jit-test/tests/warp/string-compare-char-out-of-bounds.js new file mode 100644 index 0000000000..842b009a0f --- /dev/null +++ b/js/src/jit-test/tests/warp/string-compare-char-out-of-bounds.js @@ -0,0 +1,28 @@ +// |str.char(idx) == "b"| is compiled as |str.charCodeAt(idx) == 0x62|. + +const strings = [ + "", + "a", "b", "c", + "a-", "b-", "c-", +]; + +for (let i = 0; i < 1000; ++i) { + let str = strings[i % strings.length]; + + // |j <= str.length| to test out-of-bounds access, too. + for (let j = 0; j <= str.length; ++j) { + let ch = str.charAt(j); + let code = j < str.length ? str.charCodeAt(j) : -1; + + assertEq(ch == "", code == -1); + + assertEq(ch == "b", code == 0x62); + assertEq(ch != "b", code != 0x62); + + assertEq(ch < "b", code < 0x62); + assertEq(ch <= "b", code <= 0x62); + + assertEq(ch > "b", code > 0x62); + assertEq(ch >= "b", code >= 0x62); + } +} diff --git a/js/src/jit-test/tests/warp/string-compare-char-string.js b/js/src/jit-test/tests/warp/string-compare-char-string.js new file mode 100644 index 0000000000..f56009f5a1 --- /dev/null +++ b/js/src/jit-test/tests/warp/string-compare-char-string.js @@ -0,0 +1,60 @@ +// Test comparison against single-element character strings. + +function makeComparator(code) { + var str = String.fromCharCode(code); + + return Function("ch", "code", ` + assertEq(ch == "${str}", code == ${code} && ch.length == 1); + assertEq(ch != "${str}", code != ${code} || ch.length != 1); + + assertEq(ch < "${str}", code < ${code} || (code == ${code} && ch.length < 1)); + assertEq(ch <= "${str}", code < ${code} || (code == ${code} && ch.length <= 1)); + assertEq(ch > "${str}", code > ${code} || (code == ${code} && ch.length > 1)); + assertEq(ch >= "${str}", code > ${code} || (code == ${code} && ch.length >= 1)); + `); +} + +function testCompare(strings, comparator) { + // Don't Ion compile to ensure |comparator| won't be inlined. + with ({}) ; + + for (let i = 0; i < 1000; ++i) { + let str = strings[i % strings.length]; + + comparator("", -1); + + for (let j = 0; j < str.length; ++j) { + let ch = str.charAt(j); + let code = str.charCodeAt(j); + + comparator(ch, code); + comparator(ch + "A", code); + } + } +} + +// Compare against the Latin-1 character U+0062 ('b'). +testCompare([ + "", + "a", "b", "c", + "a-", "b-", "c-", + "a\u{100}", "b\u{100}", "c\u{100}", +], makeComparator(0x62)); + +// Compare against the maximum Latin-1 character U+00FF. +testCompare([ + "", + "a", "b", "c", + "a-", "b-", "c-", + "\u{fe}", "\u{ff}", "\u{100}", + "\u{fe}-", "\u{ff}-", "\u{100}-", +], makeComparator(0xff)); + +// Compare against the Two-byte character U+0101. +testCompare([ + "", + "a", "b", "c", + "a-", "b-", "c-", + "\u{100}", "\u{101}", "\u{102}", + "\u{100}-", "\u{101}-", "\u{102}-", +], makeComparator(0x101)); diff --git a/js/src/jit-test/tests/warp/string-endswith-constant-string.js b/js/src/jit-test/tests/warp/string-endswith-constant-string.js new file mode 100644 index 0000000000..e815f8e376 --- /dev/null +++ b/js/src/jit-test/tests/warp/string-endswith-constant-string.js @@ -0,0 +1,81 @@ +// Test case to cover String.prototype.endsWith with a constant search string. +// +// String.prototype.endsWith with a short (≤32 characters) constant string is +// optimised during lowering. + +function* characters(...ranges) { + for (let [start, end] of ranges) { + for (let i = start; i <= end; ++i) { + yield i; + } + } +} + +const ascii = [...characters( + [0x41, 0x5A], // A..Z + [0x61, 0x7A], // a..z + [0x30, 0x39], // 0..9 +)]; + +const latin1 = [...characters( + [0xC0, 0xFF], // À..ÿ +)]; + +const twoByte = [...characters( + [0x100, 0x17E], // Ā..ž +)]; + +function toRope(s) { + try { + return newRope(s[0], s.substring(1)); + } catch {} + // newRope can fail when |s| fits into an inline string. In that case simply + // return the input. + return s; +} + +function atomize(s) { + return Object.keys({[s]: 0})[0]; +} + +for (let i = 1; i <= 32; ++i) { + let strings = [ascii, latin1, twoByte].flatMap(codePoints => [ + // Same string as the input. + String.fromCodePoint(...codePoints.slice(0, i)), + + // Same length as the input, but a different string. + String.fromCodePoint(...codePoints.slice(1, i + 1)), + + // Shorter string than the input. + String.fromCodePoint(...codePoints.slice(0, i - 1)), + + // Longer string than the input. + String.fromCodePoint(...codePoints.slice(0, i + 1)), + ]).flatMap(x => [ + x, + toRope(x), + newString(x, {twoByte: true}), + atomize(x), + ]); + + for (let codePoints of [ascii, latin1, twoByte]) { + let str = String.fromCodePoint(...codePoints.slice(0, i)); + + let fn = Function("strings", ` + const expected = strings.map(x => { + // Prevent Warp compilation when computing the expected results. + with ({}) ; + return x.endsWith("${str}"); + }); + + for (let i = 0; i < 250; ++i) { + let idx = i % strings.length; + let str = strings[idx]; + + let actual = str.endsWith("${str}"); + if (actual !== expected[idx]) throw new Error(); + } + `); + fn(strings); + } +} diff --git a/js/src/jit-test/tests/warp/string-indexof-constant-string-length-one.js b/js/src/jit-test/tests/warp/string-indexof-constant-string-length-one.js new file mode 100644 index 0000000000..17ae97c0ad --- /dev/null +++ b/js/src/jit-test/tests/warp/string-indexof-constant-string-length-one.js @@ -0,0 +1,74 @@ +// Test case to cover String.prototype.{indexOf,lastIndexOf,includes} with a constant string of length one. + +const strings = [ + // Empty string. + "", + + // Latin-1 string. + "abcdefgh", + + // Two-byte string. + "\u{101}\u{102}\u{103}\u{104}\u{105}\u{106}\u{107}\u{108}", +].flatMap(x => [ + x, + + // Add leading characters. + "!".repeat(10) + x, + + // Add trailing characters. + x + "!".repeat(10), +]).flatMap(x => [ + x, + + // To cover the case when the string is two-byte, but has only Latin-1 contents. + newString(x, {twoByte: true}), +]); + +const searchStrings = [ + // Latin-1 search strings: + // - at the start of the input string + "a", + // - in the middle of the input string + "d", + // - at the end of the input string + "h", + // - not in the input string + "z", + + // Two-byte search strings: + // - at the start of the input string + "\u{101}", + // - in the middle of the input string + "\u{104}", + // - at the end of the input string + "\u{108}", + // - not in the input string + "\u{1000}", +]; + +const stringFunctions = [ + "indexOf", + "lastIndexOf", + "includes", +]; + +for (let stringFunction of stringFunctions) { + for (let searchString of searchStrings) { + let fn = Function("strings", ` + const expected = strings.map(x => { + // Prevent Warp compilation when computing the expected results. + with ({}) ; + return x.${stringFunction}("${searchString}"); + }); + + for (let i = 0; i < 500; ++i) { + let idx = i % strings.length; + let str = strings[idx]; + + let actual = str.${stringFunction}("${searchString}"); + assertEq(actual, expected[idx]); + } + `); + fn(strings); + } +} diff --git a/js/src/jit-test/tests/warp/string-indexof-constant-string-length-two.js b/js/src/jit-test/tests/warp/string-indexof-constant-string-length-two.js new file mode 100644 index 0000000000..7659aee53e --- /dev/null +++ b/js/src/jit-test/tests/warp/string-indexof-constant-string-length-two.js @@ -0,0 +1,74 @@ +// Test case to cover String.prototype.{indexOf,lastIndexOf,includes} with a constant string of length two. + +const strings = [ + // Empty string. + "", + + // Latin-1 string. + "abcdefgh", + + // Two-byte string. + "\u{101}\u{102}\u{103}\u{104}\u{105}\u{106}\u{107}\u{108}", +].flatMap(x => [ + x, + + // Add leading characters. + "!".repeat(10) + x, + + // Add trailing characters. + x + "!".repeat(10), +]).flatMap(x => [ + x, + + // To cover the case when the string is two-byte, but has only Latin-1 contents. + newString(x, {twoByte: true}), +]); + +const searchStrings = [ + // Latin-1 search strings: + // - at the start of the input string + "ab", + // - in the middle of the input string + "de", + // - at the end of the input string + "gh", + // - not in the input string + "zz", + + // Two-byte search strings: + // - at the start of the input string + "\u{101}\u{102}", + // - in the middle of the input string + "\u{104}\u{105}", + // - at the end of the input string + "\u{107}\u{108}", + // - not in the input string + "\u{1000}\u{1001}", +]; + +const stringFunctions = [ + "indexOf", + "lastIndexOf", + "includes", +]; + +for (let stringFunction of stringFunctions) { + for (let searchString of searchStrings) { + let fn = Function("strings", ` + const expected = strings.map(x => { + // Prevent Warp compilation when computing the expected results. + with ({}) ; + return x.${stringFunction}("${searchString}"); + }); + + for (let i = 0; i < 500; ++i) { + let idx = i % strings.length; + let str = strings[idx]; + + let actual = str.${stringFunction}("${searchString}"); + assertEq(actual, expected[idx]); + } + `); + fn(strings); + } +} diff --git a/js/src/jit-test/tests/warp/string-indexof-constant-string.js b/js/src/jit-test/tests/warp/string-indexof-constant-string.js new file mode 100644 index 0000000000..1d737ec438 --- /dev/null +++ b/js/src/jit-test/tests/warp/string-indexof-constant-string.js @@ -0,0 +1,98 @@ +// Test case to cover String.prototype.indexOf with a constant search string. +// +// String.prototype.indexOf with a short (≤32 characters) constant string is +// optimised during lowering. + +function* characters(...ranges) { + for (let [start, end] of ranges) { + for (let i = start; i <= end; ++i) { + yield i; + } + } +} + +const ascii = [...characters( + [0x41, 0x5A], // A..Z + [0x61, 0x7A], // a..z + [0x30, 0x39], // 0..9 +)]; + +const latin1 = [...characters( + [0xC0, 0xFF], // À..ÿ +)]; + +const twoByte = [...characters( + [0x100, 0x17E], // Ā..ž +)]; + +function toRope(s) { + try { + return newRope(s[0], s.substring(1)); + } catch {} + // newRope can fail when |s| fits into an inline string. In that case simply + // return the input. + return s; +} + +function atomize(s) { + return Object.keys({[s]: 0})[0]; +} + +for (let i = 1; i <= 32; ++i) { + let strings = [ascii, latin1, twoByte].flatMap(codePoints => [ + // Same string as the input. + String.fromCodePoint(...codePoints.slice(0, i)), + + // Same length as the input, but a different string. + String.fromCodePoint(...codePoints.slice(1, i + 1)), + + // Shorter string than the input. + String.fromCodePoint(...codePoints.slice(0, i - 1)), + + // Longer string than the input. + String.fromCodePoint(...codePoints.slice(0, i + 1)), + ]).flatMap(x => [ + x, + toRope(x), + newString(x, {twoByte: true}), + atomize(x), + ]); + + for (let codePoints of [ascii, latin1, twoByte]) { + let str = String.fromCodePoint(...codePoints.slice(0, i)); + + let fn = Function("strings", ` + const expected = strings.map(x => { + // Prevent Warp compilation when computing the expected results. + with ({}) ; + return x.indexOf("${str}") === 0; + }); + + for (let i = 0; i < 250; ++i) { + let idx = i % strings.length; + let str = strings[idx]; + + let actual = str.indexOf("${str}") === 0; + if (actual !== expected[idx]) throw new Error(); + } + `); + fn(strings); + + let fnNot = Function("strings", ` + const expected = strings.map(x => { + // Prevent Warp compilation when computing the expected results. + with ({}) ; + return x.indexOf("${str}") !== 0; + }); + + for (let i = 0; i < 250; ++i) { + let idx = i % strings.length; + let str = strings[idx]; + + let actual = str.indexOf("${str}") !== 0; + if (actual !== expected[idx]) throw new Error(); + } + `); + fnNot(strings); + } +} diff --git a/js/src/jit-test/tests/warp/string-indexof-is-startswith.js b/js/src/jit-test/tests/warp/string-indexof-is-startswith.js new file mode 100644 index 0000000000..1d9f31e38c --- /dev/null +++ b/js/src/jit-test/tests/warp/string-indexof-is-startswith.js @@ -0,0 +1,29 @@ +// |str.indexOf(searchStr) == 0| is compiled as |str.startsWith(searchStr)|. +// |str.indexOf(searchStr) != 0| is compiled as |!str.startsWith(searchStr)|. + +const strings = [ + "", + "a", "b", + "ab", "ba", "ac", "ca", + "aba", "abb", "abc", "aca", +]; + +function StringIndexOf(str, searchStr) { + // Prevent Warp compilation when computing the expected result. + with ({}); + return str.indexOf(searchStr); +} + +for (let str of strings) { + for (let searchStr of strings) { + let startsWith = str.indexOf(searchStr) === 0; + + assertEq(startsWith, str.startsWith(searchStr)); + assertEq(startsWith, StringIndexOf(str, searchStr) === 0); + + let notStartsWith = str.indexOf(searchStr) !== 0; + + assertEq(notStartsWith, !str.startsWith(searchStr)); + assertEq(notStartsWith, StringIndexOf(str, searchStr) !== 0); + } +} diff --git a/js/src/jit-test/tests/warp/string-startswith-constant-string.js b/js/src/jit-test/tests/warp/string-startswith-constant-string.js new file mode 100644 index 0000000000..48a3f6dc73 --- /dev/null +++ b/js/src/jit-test/tests/warp/string-startswith-constant-string.js @@ -0,0 +1,81 @@ +// Test case to cover String.prototype.startsWith with a constant search string. +// +// String.prototype.startsWith with a short (≤32 characters) constant string is +// optimised during lowering. + +function* characters(...ranges) { + for (let [start, end] of ranges) { + for (let i = start; i <= end; ++i) { + yield i; + } + } +} + +const ascii = [...characters( + [0x41, 0x5A], // A..Z + [0x61, 0x7A], // a..z + [0x30, 0x39], // 0..9 +)]; + +const latin1 = [...characters( + [0xC0, 0xFF], // À..ÿ +)]; + +const twoByte = [...characters( + [0x100, 0x17E], // Ā..ž +)]; + +function toRope(s) { + try { + return newRope(s[0], s.substring(1)); + } catch {} + // newRope can fail when |s| fits into an inline string. In that case simply + // return the input. + return s; +} + +function atomize(s) { + return Object.keys({[s]: 0})[0]; +} + +for (let i = 1; i <= 32; ++i) { + let strings = [ascii, latin1, twoByte].flatMap(codePoints => [ + // Same string as the input. + String.fromCodePoint(...codePoints.slice(0, i)), + + // Same length as the input, but a different string. + String.fromCodePoint(...codePoints.slice(1, i + 1)), + + // Shorter string than the input. + String.fromCodePoint(...codePoints.slice(0, i - 1)), + + // Longer string than the input. + String.fromCodePoint(...codePoints.slice(0, i + 1)), + ]).flatMap(x => [ + x, + toRope(x), + newString(x, {twoByte: true}), + atomize(x), + ]); + + for (let codePoints of [ascii, latin1, twoByte]) { + let str = String.fromCodePoint(...codePoints.slice(0, i)); + + let fn = Function("strings", ` + const expected = strings.map(x => { + // Prevent Warp compilation when computing the expected results. + with ({}) ; + return x.startsWith("${str}"); + }); + + for (let i = 0; i < 250; ++i) { + let idx = i % strings.length; + let str = strings[idx]; + + let actual = str.startsWith("${str}"); + if (actual !== expected[idx]) throw new Error(); + } + `); + fn(strings); + } +} diff --git a/js/src/jit-test/tests/warp/string-substring-is-charat.js b/js/src/jit-test/tests/warp/string-substring-is-charat.js new file mode 100644 index 0000000000..0675aa2029 --- /dev/null +++ b/js/src/jit-test/tests/warp/string-substring-is-charat.js @@ -0,0 +1,13 @@ +// |str.substring(0, 1)| is compiled as |str.charAt(0)|. + +const strings = [ + "", + "a", "b", + "ab", "ba", +]; + +for (let i = 0; i < 1000; ++i) { + let str = strings[i % strings.length]; + + assertEq(str.substring(0, 1), str.charAt(0)); +} diff --git a/js/src/jit-test/tests/warp/string-substring-startswith-constant-string.js b/js/src/jit-test/tests/warp/string-substring-startswith-constant-string.js new file mode 100644 index 0000000000..f98627cfe9 --- /dev/null +++ b/js/src/jit-test/tests/warp/string-substring-startswith-constant-string.js @@ -0,0 +1,90 @@ +// Test case to cover String.prototype.substring as startsWith with a constant search string. +// +// String.prototype.substring comparison with a short (≤32 characters) constant string is +// optimised during lowering. + +function* characters(...ranges) { + for (let [start, end] of ranges) { + for (let i = start; i <= end; ++i) { + yield i; + } + } +} + +const ascii = [...characters( + [0x41, 0x5A], // A..Z + [0x61, 0x7A], // a..z + [0x30, 0x39], // 0..9 +)]; + +const latin1 = [...characters( + [0xC0, 0xFF], // À..ÿ +)]; + +const twoByte = [...characters( + [0x100, 0x17E], // Ā..ž +)]; + +function toRope(s) { + try { + return newRope(s[0], s.substring(1)); + } catch {} + // newRope can fail when |s| fits into an inline string. In that case simply + // return the input. + return s; +} + +function atomize(s) { + return Object.keys({[s]: 0})[0]; +} + +const operators = [ + "==", "===", "!=", "!==", +]; + +for (let i = 1; i <= 32; ++i) { + let strings = [ascii, latin1, twoByte].flatMap(codePoints => [ + // Same string as the input. + String.fromCodePoint(...codePoints.slice(0, i)), + + // Same length as the input, but a different string. + String.fromCodePoint(...codePoints.slice(1, i + 1)), + + // Shorter string than the input. + String.fromCodePoint(...codePoints.slice(0, i - 1)), + + // Longer string than the input. + String.fromCodePoint(...codePoints.slice(0, i + 1)), + ]).flatMap(x => [ + x, + toRope(x), + newString(x, {twoByte: true}), + atomize(x), + ]); + + for (let codePoints of [ascii, latin1, twoByte]) { + let str = String.fromCodePoint(...codePoints.slice(0, i)); + + for (let op of operators) { + let fn = Function("strings", ` + const expected = strings.map(x => { + // Prevent Warp compilation when computing the expected results. + with ({}) ; + return x.substring(0, ${str.length}) ${op} "${str}"; + }); + + for (let i = 0; i < 250; ++i) { + let idx = i % strings.length; + let str = strings[idx]; + + let lhs = str.substring(0, ${str.length}) ${op} "${str}"; + if (lhs !== expected[idx]) throw new Error(); + + let rhs = "${str}" ${op} str.substring(0, ${str.length}); + if (rhs !== expected[idx]) throw new Error(); + } + `); + fn(strings); + } + } +} diff --git a/js/src/jit-test/tests/warp/string-substring-static-strings.js b/js/src/jit-test/tests/warp/string-substring-static-strings.js new file mode 100644 index 0000000000..1ea8e36c31 --- /dev/null +++ b/js/src/jit-test/tests/warp/string-substring-static-strings.js @@ -0,0 +1,20 @@ +// `str.substring(...)` can return static strings. + +const strings = [ + "abcdef", + "ABCDEF", +]; + +for (let i = 0; i < 500; ++i) { + let str = strings[i & 1]; + + for (let j = 0; j < 2; ++j) { + // One element static string. + let r = str.substring(j, j + 1); + assertEq(r, str.charAt(j)); + + // Two elements static string. + let s = str.substring(j, j + 2); + assertEq(s, str.charAt(j) + str.charAt(j + 1)); + } +} diff --git a/js/src/jit-test/tests/warp/string-tolowercase-latin1.js b/js/src/jit-test/tests/warp/string-tolowercase-latin1.js new file mode 100644 index 0000000000..2d94850445 --- /dev/null +++ b/js/src/jit-test/tests/warp/string-tolowercase-latin1.js @@ -0,0 +1,85 @@ +// Test inline lower case conversion of ASCII / Latin-1 strings. + +function* characters(...ranges) { + for (let [start, end] of ranges) { + for (let i = start; i <= end; ++i) { + yield i; + } + } +} + +const ascii_upper = [...characters( + [0x41, 0x5A], // A..Z + [0x30, 0x39], // 0..9 +)]; + +const ascii_lower = [...characters( + [0x61, 0x7A], // a..z + [0x30, 0x39], // 0..9 +)]; + +const latin1_upper = [...characters( + [0xC0, 0xDE], // À..Þ + [0x30, 0x39], // 0..9 +)]; + +const latin1_lower = [...characters( + [0xDF, 0xFF], // ß..ÿ +)]; + +for (let upper of [ascii_upper, latin1_upper]) { + let s = String.fromCodePoint(...upper); + assertEq(isLatin1(s), true); + assertEq(s, s.toUpperCase()); +} + +for (let lower of [ascii_lower, latin1_lower]) { + let s = String.fromCodePoint(...lower); + assertEq(isLatin1(s), true); + assertEq(s, s.toLowerCase()); +} + +function toRope(s) { + try { + return newRope(s[0], s.substring(1)); + } catch {} + // newRope can fail when |s| fits into an inline string. In that case simply + // return the input. + return s; +} + +for (let i = 0; i <= 32; ++i) { + let strings = [ascii_upper, ascii_lower, latin1_upper, latin1_lower].flatMap(codePoints => [ + String.fromCodePoint(...codePoints.slice(0, i)), + + // Trailing ASCII upper case character. + String.fromCodePoint(...codePoints.slice(0, i)) + "A", + + // Trailing ASCII lower case character. + String.fromCodePoint(...codePoints.slice(0, i)) + "a", + + // Trailing Latin-1 upper case character. + String.fromCodePoint(...codePoints.slice(0, i)) + "À", + + // Trailing Latin-1 lower case character. + String.fromCodePoint(...codePoints.slice(0, i)) + "ß", + ]).flatMap(x => [ + x, + toRope(x), + newString(x, {twoByte: true}), + ]); + + const expected = strings.map(x => { + // Prevent Warp compilation when computing the expected results. + with ({}) ; + return x.toLowerCase(); + }); + + for (let i = 0; i < 1000; ++i) { + let idx = i % strings.length; + let str = strings[idx]; + + let actual = str.toLowerCase(); + if (actual !== expected[idx]) throw new Error(); + } +} diff --git a/js/src/jit-test/tests/warp/string-totitlecase.js b/js/src/jit-test/tests/warp/string-totitlecase.js new file mode 100644 index 0000000000..0237a263b3 --- /dev/null +++ b/js/src/jit-test/tests/warp/string-totitlecase.js @@ -0,0 +1,85 @@ +// Test inline title case conversion. + +function* characters(...ranges) { + for (let [start, end] of ranges) { + for (let i = start; i <= end; ++i) { + yield i; + } + } +} + +const ascii = [...characters( + [0x41, 0x5A], // A..Z + [0x61, 0x7A], // a..z + [0x30, 0x39], // 0..9 +)]; + +const latin1 = [...characters( + [0xC0, 0xFF], // À..ÿ +)]; + +const twoByte = [...characters( + [0x100, 0x17E], // Ā..ž +)]; + +String.prototype.toTitleCase = function() { + "use strict"; + + var s = String(this); + + if (s.length === 0) { + return s; + } + return s[0].toUpperCase() + s.substring(1); +}; + +function toRope(s) { + try { + return newRope(s[0], s.substring(1)); + } catch {} + // newRope can fail when |s| fits into an inline string. In that case simply + // return the input. + return s; +} + +for (let i = 0; i <= 32; ++i) { + let strings = [ascii, latin1, twoByte].flatMap(codePoints => [ + String.fromCodePoint(...codePoints.slice(0, i)), + + // Leading ASCII upper case character. + "A" + String.fromCodePoint(...codePoints.slice(0, i)), + + // Leading ASCII lower case character. + "a" + String.fromCodePoint(...codePoints.slice(0, i)), + + // Leading Latin-1 upper case character. + "À" + String.fromCodePoint(...codePoints.slice(0, i)), + + // Leading Latin-1 lower case character. + "à" + String.fromCodePoint(...codePoints.slice(0, i)), + + // Leading Two-Byte upper case character. + "Ā" + String.fromCodePoint(...codePoints.slice(0, i)), + + // Leading Two-Byte lower case character. + "ā" + String.fromCodePoint(...codePoints.slice(0, i)), + ]).flatMap(x => [ + x, + toRope(x), + newString(x, {twoByte: true}), + ]); + + const expected = strings.map(x => { + // Prevent Warp compilation when computing the expected results. + with ({}) ; + return x.toTitleCase(); + }); + + for (let i = 0; i < 1000; ++i) { + let idx = i % strings.length; + let str = strings[idx]; + + let actual = str.toTitleCase(); + if (actual !== expected[idx]) throw new Error(); + } +} diff --git a/js/src/jit-test/tests/warp/string-trim.js b/js/src/jit-test/tests/warp/string-trim.js new file mode 100644 index 0000000000..0ca05be8ac --- /dev/null +++ b/js/src/jit-test/tests/warp/string-trim.js @@ -0,0 +1,75 @@ +const whitespace = [ + // Ascii whitespace + " ", + "\t", + + // Latin-1 whitespace + "\u{a0}", + + // Two-byte whitespace + "\u{1680}", +]; + +const strings = [ + // Empty string + "", + + // Ascii strings + "a", + "abc", + + // Latin-1 strings + "á", + "áèô", + + // Two-byte strings + "\u{100}", + "\u{100}\u{101}\u{102}", +].flatMap(x => [ + x, + + // Leading whitespace + ...whitespace.flatMap(w => [w + x, w + w + x]), + + // Trailing whitespace + ...whitespace.flatMap(w => [x + w, x + w + w]), + + // Leading and trailing whitespace + ...whitespace.flatMap(w => [w + x + w, w + w + x + w + w]), + + // Interspersed whitespace + ...whitespace.flatMap(w => [x + w + x, x + w + w + x]), +]); + +function trim(strings) { + // Compute expected values using RegExp. + let expected = strings.map(x => x.replace(/(^\s+)|(\s+$)/g, "")); + + for (let i = 0; i < 1000; ++i) { + let index = i % strings.length; + assertEq(strings[index].trim(), expected[index]); + } +} +for (let i = 0; i < 2; ++i) trim(strings); + +function trimStart(strings) { + // Compute expected values using RegExp. + let expected = strings.map(x => x.replace(/(^\s+)/g, "")); + + for (let i = 0; i < 1000; ++i) { + let index = i % strings.length; + assertEq(strings[index].trimStart(), expected[index]); + } +} +for (let i = 0; i < 2; ++i) trimStart(strings); + +function trimEnd(strings) { + // Compute expected values using RegExp. + let expected = strings.map(x => x.replace(/(\s+$)/g, "")); + + for (let i = 0; i < 1000; ++i) { + let index = i % strings.length; + assertEq(strings[index].trimEnd(), expected[index]); + } +} +for (let i = 0; i < 2; ++i) trimEnd(strings); diff --git a/js/src/jit-test/tests/warp/stub-folding-add-case.js b/js/src/jit-test/tests/warp/stub-folding-add-case.js new file mode 100644 index 0000000000..a31b0f2e7e --- /dev/null +++ b/js/src/jit-test/tests/warp/stub-folding-add-case.js @@ -0,0 +1,35 @@ +// |jit-test| --fast-warmup + +var sum = 0; +function foo(o) { + sum += o.a; +} + +with({}) {} + +// Fold a stub with two cases. +for (var i = 0; i < 11; i++) { + foo({a:1}); + foo({a:1,b:2}); +} + +// Add a third case. +foo({a:1,b:2,c:3}); + +// Warp-compile. +for (var i = 0; i < 16; i++) { + foo({a:1}); +} + +// Add a fourth case. +foo({a:1,b:2,c:3,d:4}); + +// Run for a while in Warp. +for (var i = 0; i < 20; i++) { + foo({a:1}); + foo({a:1,b:2}); + foo({a:1,b:2,c:3}); + foo({a:1,b:2,c:3,d:4}); +} + +assertEq(sum, 120); diff --git a/js/src/jit-test/tests/warp/stub-folding-cross-compartment.js b/js/src/jit-test/tests/warp/stub-folding-cross-compartment.js new file mode 100644 index 0000000000..735504cf89 --- /dev/null +++ b/js/src/jit-test/tests/warp/stub-folding-cross-compartment.js @@ -0,0 +1,15 @@ +var src = ` +var p = {y: 1}; +var arr = []; +for (var i = 0; i < 10; i++) { + var o = Object.create(p); + o["x" + i] = 2; + arr.push(o); +} +arr +`; + +var wrapper = evaluate(src, {global: newGlobal({sameZoneAs: this})}); +for (var i = 0; i < 50; i++) { + assertEq(wrapper[i % 10].y, 1); +} diff --git a/js/src/jit-test/tests/warp/stub-folding-transition.js b/js/src/jit-test/tests/warp/stub-folding-transition.js new file mode 100644 index 0000000000..865e6dc189 --- /dev/null +++ b/js/src/jit-test/tests/warp/stub-folding-transition.js @@ -0,0 +1,24 @@ +var sum = 0; +function foo(o) { + sum += o.x; +} + +with({}) {} + +// Trigger stub folding in MaybeTransition +for (var i = 0; i < 200; i++) { + foo({x:1, a:1}); + foo({x:1, b:1}); + foo({x:1, c:1}); + foo({x:1, d:1}); + foo({x:1, e:1}); + foo({x:1, f:1}); + foo({x:1, g:1}); + foo({x:1, h:1}); + foo({x:1, i:1}); + foo({x:1, j:1}); + foo({x:1, k:1}); + foo({x:1, l:1}); +} + +assertEq(sum, 2400); diff --git a/js/src/jit-test/tests/warp/stub-folding.js b/js/src/jit-test/tests/warp/stub-folding.js new file mode 100644 index 0000000000..0b7d7fdda4 --- /dev/null +++ b/js/src/jit-test/tests/warp/stub-folding.js @@ -0,0 +1,35 @@ +// |jit-test| --fast-warmup + +with({}) {} + +class A { + foo() { return 3; } + get z() { return 5; } +} + +class B1 extends A { + constructor(y) { + super(); + this.y = y; + } +} + +class B2 extends A { + constructor(x,y) { + super(); + this.y = y; + this.x = x; + } +} + +var sum = 0; +function foo(o) { + sum += o.foo() + o.y + o.z; +} + +for (var i = 0; i < 50; i++) { + foo(new B1(i)); + foo(new B2(i,i)); +} + +assertEq(sum, 3250); diff --git a/js/src/jit-test/tests/warp/super-native-newtarget.js b/js/src/jit-test/tests/warp/super-native-newtarget.js new file mode 100644 index 0000000000..3f9fb24a3a --- /dev/null +++ b/js/src/jit-test/tests/warp/super-native-newtarget.js @@ -0,0 +1,20 @@ +class A {} + +class B extends A { + constructor() { + super(); + } +} + +function h() {} +h = h.bind(); + +function f() { + for (var i = 0; i < 1000; ++i) { + var o = Reflect.construct(B, [], h); + } +} + +for (var i = 0; i < 5; ++i) { + f(); +} diff --git a/js/src/jit-test/tests/warp/throw-exception-stack-location.js b/js/src/jit-test/tests/warp/throw-exception-stack-location.js new file mode 100644 index 0000000000..bcb7329b05 --- /dev/null +++ b/js/src/jit-test/tests/warp/throw-exception-stack-location.js @@ -0,0 +1,41 @@ +function throwValue(value) { + throw value; +} + +// Test try-finally keep the exception stack. +function testFinally() { + function f() { + try { + throwValue("exception-value"); + } finally { + for (let i = 0; i < 100; ++i) { + // OSR + } + } + } + + let info = getExceptionInfo(f); + assertEq(info.exception, "exception-value"); + assertEq(info.stack.includes("throwValue"), true); +} +testFinally(); + +// Test try-catch-finally keep the exception stack. +function testCatchFinally() { + function f() { + try { + throw null; + } catch { + throwValue("exception-value"); + } finally { + for (let i = 0; i < 100; ++i) { + // OSR + } + } + } + + let info = getExceptionInfo(f); + assertEq(info.exception, "exception-value"); + assertEq(info.stack.includes("throwValue"), true); +} +testCatchFinally(); diff --git a/js/src/jit-test/tests/warp/trial-inline-gc-1.js b/js/src/jit-test/tests/warp/trial-inline-gc-1.js new file mode 100644 index 0000000000..6129221bb8 --- /dev/null +++ b/js/src/jit-test/tests/warp/trial-inline-gc-1.js @@ -0,0 +1,14 @@ +// |jit-test| --fast-warmup +function g(x) { + return x + 1; +} +function f() { + for (var i = 0; i < 150; i++) { + assertEq(g("foo"), "foo1"); + assertEq(g(1), 2); + if (i === 100) { + gc(this, "shrinking"); + } + } +} +f(); diff --git a/js/src/jit-test/tests/warp/trial-inline-gc-2.js b/js/src/jit-test/tests/warp/trial-inline-gc-2.js new file mode 100644 index 0000000000..058e2ff645 --- /dev/null +++ b/js/src/jit-test/tests/warp/trial-inline-gc-2.js @@ -0,0 +1,18 @@ +// |jit-test| --fast-warmup +function h(i) { + with(this) {} // Don't inline. + if (i === 100) { + gc(this, "shrinking"); + } +} +function g(i, x) { + h(i); + return x + 1; +} +function f() { + for (var i = 0; i < 200; i++) { + g(i, "foo"); + g(i, 1); + } +} +f(); diff --git a/js/src/jit-test/tests/warp/trial-inline-gc-3.js b/js/src/jit-test/tests/warp/trial-inline-gc-3.js new file mode 100644 index 0000000000..4e64a22167 --- /dev/null +++ b/js/src/jit-test/tests/warp/trial-inline-gc-3.js @@ -0,0 +1,23 @@ +// |jit-test| --fast-warmup +function h(i) { + if (i === 150) { + with(this) {} // Don't inline. + // Trigger an invalidation of f's IonScript (with g inlined into it) on + // the stack. Before we bail out, replace g and trial-inline the new + // function. The bailout must not get confused by this. + gc(this, "shrinking"); + g = (i, x) => x + 20; + f(); + } +} +function g(i, x) { + h(i); + return x + 1; +} +function f() { + for (var i = 0; i < 300; i++) { + g(i, "foo"); + g(i, 1); + } +} +f(); diff --git a/js/src/jit-test/tests/warp/try-catch-unwind.js b/js/src/jit-test/tests/warp/try-catch-unwind.js new file mode 100644 index 0000000000..a167e03f5d --- /dev/null +++ b/js/src/jit-test/tests/warp/try-catch-unwind.js @@ -0,0 +1,29 @@ +let throwing = false; + +function bar() { + with ({}) {} + if (throwing) throw 3; +} + +function foo() { + let y = 3; + try { + let x = 3; + () => { return x + y; } + bar(); + } catch (e) { + assertEq(y, 3); + throw e; + } +} + +with ({}) {} + +for (var i = 0; i < 1000; i++) { + foo() +} + +throwing = true; +try { + foo(); +} catch {} diff --git a/js/src/jit-test/tests/warp/try-finally-1.js b/js/src/jit-test/tests/warp/try-finally-1.js new file mode 100644 index 0000000000..206286d598 --- /dev/null +++ b/js/src/jit-test/tests/warp/try-finally-1.js @@ -0,0 +1,36 @@ +let target = 3; +function throwOn(x) { + with ({}) {} + if (x == target) { + throw 3; + } +} + +// Test try-finally without catch +function foo() { + var x = 0; + try { + throwOn(0); + x = 1; + throwOn(1); + x = 2; + throwOn(2); + x = 3; + } finally { + assertEq(x, target); + } +} + +with ({}) {} +for (var i = 0; i < 1000; i++) { + try { + foo(); + } catch {} +} + +for (var i = 0; i < 4; i++) { + target = i; + try { + foo(); + } catch {} +} diff --git a/js/src/jit-test/tests/warp/try-finally-2.js b/js/src/jit-test/tests/warp/try-finally-2.js new file mode 100644 index 0000000000..4187a37e0c --- /dev/null +++ b/js/src/jit-test/tests/warp/try-finally-2.js @@ -0,0 +1,42 @@ +let catchCount = 0; + +let target = 3; +function throwOn(x) { + with ({}) {} + if (x == target) { + throw 3; + } +} + +// Test try-catch-finally +function foo() { + var x = 0; + try { + throwOn(0); + x = 1; + throwOn(1); + x = 2; + throwOn(2); + x = 3; + } catch { + catchCount++; + } finally { + assertEq(x, target); + } +} + +with ({}) {} +for (var i = 0; i < 1000; i++) { + try { + foo(); + } catch {} +} + +for (var i = 0; i < 4; i++) { + target = i; + try { + foo(); + } catch {} +} + +assertEq(catchCount, 3); diff --git a/js/src/jit-test/tests/warp/try-finally-3.js b/js/src/jit-test/tests/warp/try-finally-3.js new file mode 100644 index 0000000000..98811486db --- /dev/null +++ b/js/src/jit-test/tests/warp/try-finally-3.js @@ -0,0 +1,35 @@ +var target = undefined; +function throwOn(x) { + with ({}) {} + if (x === target) { + throw x; + } +} + +// Test nested try-finally +function foo() { + let result = 0; + try { + throwOn(1); + result += 1; + try { + throwOn(2); + result += 2; + } finally { + result += 3; + } + } finally { + return result; + } +} + +with ({}) {} +for (var i = 0; i < 100; i++) { + assertEq(foo(), 6); +} + +target = 1; +assertEq(foo(), 0); + +target = 2; +assertEq(foo(), 4); diff --git a/js/src/jit-test/tests/warp/try-finally-unwind.js b/js/src/jit-test/tests/warp/try-finally-unwind.js new file mode 100644 index 0000000000..05860fbaa5 --- /dev/null +++ b/js/src/jit-test/tests/warp/try-finally-unwind.js @@ -0,0 +1,28 @@ +let throwing = false; + +function bar() { + with ({}) {} + if (throwing) throw 3; +} + +function foo() { + let y = 3; + try { + let x = 3; + () => { return x + y; } + bar(); + } finally { + assertEq(y, 3); + } +} + +with ({}) {} + +for (var i = 0; i < 1000; i++) { + foo() +} + +throwing = true; +try { + foo(); +} catch {} diff --git a/js/src/jit-test/tests/warp/typedarray-element-exists.js b/js/src/jit-test/tests/warp/typedarray-element-exists.js new file mode 100644 index 0000000000..623ad672cb --- /dev/null +++ b/js/src/jit-test/tests/warp/typedarray-element-exists.js @@ -0,0 +1,46 @@ +function inBounds() { + var ta = new Int32Array(10); + + for (var i = 0; i < 100; ++i) { + var index = i & 7; + assertEq(index in ta, true); + } +} +inBounds(); + +function outOfBounds() { + var ta = new Int32Array(10); + + for (var i = 0; i < 100; ++i) { + var index = 10 + (i & 7); + assertEq(index in ta, false); + + var largeIndex = 2147483647 - (i & 1); + assertEq(largeIndex in ta, false); + } +} +outOfBounds(); + +function negativeIndex() { + var ta = new Int32Array(10); + + for (var i = 0; i < 100; ++i) { + var index = -(1 + (i & 7)); + assertEq(index in ta, false); + + var largeIndex = -2147483647 - (i & 1); + assertEq(largeIndex in ta, false); + } +} +negativeIndex(); + +function emptyArray() { + var ta = new Int32Array(0); + + for (var i = 0; i < 100; ++i) { + var index = i & 7; + assertEq(index in ta, false); + assertEq(-index in ta, false); + } +} +emptyArray(); diff --git a/js/src/jit-test/tests/warp/typedarrayindextoint32.js b/js/src/jit-test/tests/warp/typedarrayindextoint32.js new file mode 100644 index 0000000000..5333ada53e --- /dev/null +++ b/js/src/jit-test/tests/warp/typedarrayindextoint32.js @@ -0,0 +1,10 @@ +function f(ta, i) { + var x = i + 0.2; + return ta[i] + ta[i | 0] + ta[x - 0.2]; +} + +var ta = new Int32Array(10); +var xs = [0, 1, 2, -1]; +for (var i = 0; i < 100_000; ++i) { + assertEq(f(ta, xs[i & 3]), (i & 3) == 3 ? NaN : 0); +} diff --git a/js/src/jit-test/tests/warp/typeof-is.js b/js/src/jit-test/tests/warp/typeof-is.js new file mode 100644 index 0000000000..df41185769 --- /dev/null +++ b/js/src/jit-test/tests/warp/typeof-is.js @@ -0,0 +1,126 @@ +// Test case |typeof| folding in simple comparison contexts. + +// Create functions to test if |typeof x| is equal to a constant string. +// - Covers all possible |typeof| results, plus the invalid string "bad". +// - Covers all four possible equality comparison operators. +function createFunctions() { + return [ + "undefined", + "object", + "function", + "string", + "number", + "boolean", + "symbol", + "bigint", + "bad", + ].flatMap(type => [ + "==", + "===", + "!=", + "!==" + ].map(op => ({ + type, + equal: op[0] === "=", + fn: Function("thing", `return typeof thing ${op} "${type}"`) + }))); +} + +let functions = createFunctions(); + +function test() { + const ccwGlobal = newGlobal({newCompartment: true}); + const xs = [ + // "undefined" + // Plain undefined and object emulating undefined, including various proxy wrapper cases. + undefined, + createIsHTMLDDA(), + wrapWithProto(createIsHTMLDDA(), null), + ccwGlobal.eval("createIsHTMLDDA()"), + + // "object" + // Plain object and various proxy wrapper cases. + {}, + this, + new Proxy({}, {}), + wrapWithProto({}, null), + transplantableObject({proxy: true}).object, + ccwGlobal.Object(), + + // "function" + // Plain function and various proxy wrapper cases. + function(){}, + new Proxy(function(){}, {}), + new Proxy(createIsHTMLDDA(), {}), + wrapWithProto(function(){}, null), + ccwGlobal.Function(), + + // "string" + "", + + // "number" + // Int32 and Double numbers. + 0, + 1.23, + + // "boolean" + true, + + // "symbol" + Symbol(), + + // "bigint" + 0n, + ]; + + for (let i = 0; i < 200; ++i) { + let x = xs[i % xs.length]; + for (let {type, equal, fn} of functions) { + assertEq(fn(x), (typeof x === type) === equal); + } + } +} +for (let i = 0; i < 2; ++i) test(); + +// Fresh set of functions to gather new type info. +let functionsObject = createFunctions(); + +// Additional test when the input is always an object. +function testObject() { + const ccwGlobal = newGlobal({newCompartment: true}); + + // All elements of this array are objects to cover the case when |MTypeOf| has + // a |MIRType::Object| input. + const xs = [ + // "undefined" + // Object emulating undefined, including various proxy wrapper cases. + createIsHTMLDDA(), + wrapWithProto(createIsHTMLDDA(), null), + ccwGlobal.eval("createIsHTMLDDA()"), + + // "object" + // Plain object and various proxy wrapper cases. + {}, + this, + new Proxy({}, {}), + wrapWithProto({}, null), + transplantableObject({proxy: true}).object, + ccwGlobal.Object(), + + // "function" + // Plain function and various proxy wrapper cases. + function(){}, + new Proxy(function(){}, {}), + new Proxy(createIsHTMLDDA(), {}), + wrapWithProto(function(){}, null), + ccwGlobal.Function(), + ]; + + for (let i = 0; i < 200; ++i) { + let x = xs[i % xs.length]; + for (let {type, equal, fn} of functionsObject) { + assertEq(fn(x), (typeof x === type) === equal); + } + } +} +for (let i = 0; i < 2; ++i) testObject(); diff --git a/js/src/jit-test/tests/warp/typeof-switch.js b/js/src/jit-test/tests/warp/typeof-switch.js new file mode 100644 index 0000000000..4dc33c3686 --- /dev/null +++ b/js/src/jit-test/tests/warp/typeof-switch.js @@ -0,0 +1,78 @@ +// Test case |typeof| folding in switch-statement contexts. + +function TypeOf(thing) { + switch (typeof thing) { + case "undefined": + return "undefined"; + case "object": + return "object"; + case "function": + return "function"; + case "string": + return "string"; + case "number": + return "number"; + case "boolean": + return "boolean"; + case "symbol": + return "symbol"; + case "bigint": + return "bigint"; + case "bad": + return "bad"; + } + return "bad2"; +} + +function test() { + const ccwGlobal = newGlobal({newCompartment: true}); + const xs = [ + // "undefined" + // Plain undefined and objects emulating undefined, including various + // proxy wrapper cases. + undefined, + createIsHTMLDDA(), + wrapWithProto(createIsHTMLDDA(), null), + ccwGlobal.eval("createIsHTMLDDA()"), + + // "object" + // Plain objects and various proxy wrapper cases. + {}, + this, + new Proxy({}, {}), + wrapWithProto({}, null), + transplantableObject({proxy: true}).object, + ccwGlobal.Object(), + + // "function" + // Plain functions and various proxy wrapper cases. + function(){}, + new Proxy(function(){}, {}), + new Proxy(createIsHTMLDDA(), {}), + wrapWithProto(function(){}, null), + ccwGlobal.Function(), + + // "string" + "", + + // "number" + // Int32 and Double numbers. + 0, + 1.23, + + // "boolean" + true, + + // "symbol" + Symbol(), + + // "bigint" + 0n, + ]; + + for (let i = 0; i < 500; ++i) { + let x = xs[i % xs.length]; + assertEq(TypeOf(x), typeof x); + } +} +for (let i = 0; i < 2; ++i) test(); |