diff options
Diffstat (limited to '')
69 files changed, 1523 insertions, 59 deletions
diff --git a/js/src/jit-test/lib/pretenure.js b/js/src/jit-test/lib/pretenure.js index 214f1d44d1..c1184fefc4 100644 --- a/js/src/jit-test/lib/pretenure.js +++ b/js/src/jit-test/lib/pretenure.js @@ -22,8 +22,12 @@ function setupPretenureTest() { gczeal(0); // Restrict nursery size so we can fill it quicker, and ensure it is resized. - gcparam("minNurseryBytes", 1024 * 1024); - gcparam("maxNurseryBytes", 1024 * 1024); + let size = 1024 * 1024; + if (gcparam("semispaceNurseryEnabled")) { + size *= 2; + } + gcparam("minNurseryBytes", size); + gcparam("maxNurseryBytes", size); // Limit allocation threshold so we trigger major GCs sooner. gcparam("allocationThreshold", 1 /* MB */); @@ -67,8 +71,14 @@ function allocateArrays(count, longLived) { } function gcCounts() { - return { minor: gcparam("minorGCNumber"), - major: gcparam("majorGCNumber") }; + let major = gcparam("majorGCNumber") + let minor = gcparam("minorGCNumber"); + + // Only report minor collections that didn't happen as part of a major GC. + assertEq(minor >= major, true); + minor -= major; + + return { minor, major }; } function runTestAndCountCollections(thunk) { diff --git a/js/src/jit-test/tests/arrays/sort-trampoline.js b/js/src/jit-test/tests/arrays/sort-trampoline.js new file mode 100644 index 0000000000..b81d4628b0 --- /dev/null +++ b/js/src/jit-test/tests/arrays/sort-trampoline.js @@ -0,0 +1,153 @@ +function testGC() { + var arr = [{n: 1}, {n: 3}, {n: 0}, {n: 5}]; + for (var i = 0; i < 20; i++) { + arr.sort((x, y) => { + if (i === 17) { + gc(); + } + return x.n - y.n; + }); + } + assertEq(arr.map(x => x.n).join(""), "0135"); +} +testGC(); + +function testException() { + var arr = [{n: 1}, {n: 3}, {n: 0}, {n: 5}]; + var ex; + try { + for (var i = 0; i < 20; i++) { + arr.sort((x, y) => { + if (i === 17) { + throw "fit"; + } + return x.n - y.n; + }); + } + } catch (e) { + ex = e; + } + assertEq(ex, "fit"); + assertEq(i, 17); +} +testException(); + +function testRectifier() { + var arr = [{n: 1}, {n: 3}, {n: 0}, {n: 5}]; + for (var i = 0; i < 20; i++) { + arr.sort(function(x, y, a) { + assertEq(arguments.length, 2); + assertEq(a, undefined); + return y.n - x.n; + }); + } + assertEq(arr.map(x => x.n).join(""), "5310"); +} +testRectifier(); + +function testClassConstructor() { + var normal = (x, y) => x.n - y.n; + var dummy = {}; + var ctor = (class { constructor(x, y) { + assertEq(x, dummy); + }}); + // Warm up the constructor. + for (var i = 0; i < 20; i++) { + new ctor(dummy, dummy); + } + for (var i = 0; i < 20; i++) { + var arr = [{n: 1}, {n: 3}, {n: 0}, {n: 5}]; + var ex; + try { + arr.sort(i < 17 ? normal : ctor); + } catch (e) { + ex = e; + } + assertEq(ex instanceof TypeError, i >= 17); + assertEq(arr.map(x => x.n).join(""), i >= 17 ? "1305" : "0135"); + } +} +testClassConstructor(); + +function testSwitchRealms() { + var arr = [{n: 1}, {n: 3}, {n: 0}, {n: 5}]; + var g = newGlobal({sameCompartmentAs: this}); + g.foo = 123; + var comp = g.evaluate(`((x, y) => { + assertEq(foo, 123); + return x.n - y.n; + })`); + for (var i = 0; i < 20; i++) { + arr.sort(comp); + } + assertEq(arr.map(x => x.n).join(""), "0135"); +} +testSwitchRealms(); + +function testCrossCompartment() { + var arr = [{n: 1}, {n: 3}, {n: 0}, {n: 5}]; + var g = newGlobal({newCompartment: true}); + var wrapper = g.evaluate(`((x, y) => { + return x.n - y.n; + })`); + for (var i = 0; i < 20; i++) { + arr.sort(wrapper); + } + assertEq(arr.map(x => x.n).join(""), "0135"); +} +testCrossCompartment(); + +function testBound() { + var arr = [{n: 1}, {n: 3}, {n: 0}, {n: 5}]; + var fun = (function(a, x, y) { + "use strict"; + assertEq(this, null); + assertEq(a, 1); + return x.n - y.n; + }).bind(null, 1); + for (var i = 0; i < 20; i++) { + arr.sort(fun); + } + assertEq(arr.map(x => x.n).join(""), "0135"); +} +testBound(); + +function testExtraArgs() { + var arr = [{n: 1}, {n: 3}, {n: 0}, {n: 5}]; + var cmp = (x, y) => x.n - y.n; + for (var i = 0; i < 20; i++) { + arr.sort(cmp, cmp, cmp, cmp, cmp, cmp, cmp); + } + assertEq(arr.map(x => x.n).join(""), "0135"); +} +testExtraArgs(); + +function testBailout() { + var arr = [{n: 1}, {n: 3}, {n: 0}, {n: 5}]; + for (var i = 0; i < 110; i++) { + arr.sort(function(x, y) { + if (i === 108) { + bailout(); + } + return x.n - y.n; + }); + } + assertEq(arr.map(x => x.n).join(""), "0135"); +} +testBailout(); + +function testExceptionHandlerSwitchRealm() { + var g = newGlobal({sameCompartmentAs: this}); + for (var i = 0; i < 25; i++) { + var ex = null; + try { + g.Array.prototype.toSorted.call([2, 3], () => { + throw "fit"; + }); + } catch (e) { + ex = e; + } + assertEq(ex, "fit"); + } +} +testExceptionHandlerSwitchRealm(); diff --git a/js/src/jit-test/tests/basic/bug1875795.js b/js/src/jit-test/tests/basic/bug1875795.js new file mode 100644 index 0000000000..1a5b54acfe --- /dev/null +++ b/js/src/jit-test/tests/basic/bug1875795.js @@ -0,0 +1,7 @@ +// |jit-test| --fast-warmup; --no-threads; skip-if: !('oomTest' in this) +oomTest(function() { + var o = {}; + for (var p in this) { + o[p] = 1; + } +}); diff --git a/js/src/jit-test/tests/basic/bug1888746.js b/js/src/jit-test/tests/basic/bug1888746.js new file mode 100644 index 0000000000..8e6d0cd0b9 --- /dev/null +++ b/js/src/jit-test/tests/basic/bug1888746.js @@ -0,0 +1,12 @@ +function comparator(x, y) { + saveStack(); + return {valueOf: function() { + saveStack(); + return x - y; + }}; +} +for (let i = 0; i < 20; i++) { + let arr = [3, 1, 2]; + arr.sort(comparator); + assertEq(arr.toString(), "1,2,3"); +} diff --git a/js/src/jit-test/tests/basic/bug1890200.js b/js/src/jit-test/tests/basic/bug1890200.js new file mode 100644 index 0000000000..caa97fcece --- /dev/null +++ b/js/src/jit-test/tests/basic/bug1890200.js @@ -0,0 +1,12 @@ +let triggerGC = false; +let proxy = new Proxy({}, {get: function(target, key) { + if (key === "sameCompartmentAs" || key === "sameZoneAs") { + triggerGC = true; + return newGlobal({newCompartment: true}); + } + if (triggerGC) { + gc(); + triggerGC = false; + } +}}); +newGlobal(proxy); diff --git a/js/src/jit-test/tests/cacheir/bug1888346.js b/js/src/jit-test/tests/cacheir/bug1888346.js new file mode 100644 index 0000000000..8e63d86089 --- /dev/null +++ b/js/src/jit-test/tests/cacheir/bug1888346.js @@ -0,0 +1,8 @@ +setJitCompilerOption("ion.frequent-bailout-threshold", 1); +for (let i = 0; i < 49; i++) { + (function () { + let x = new (function () {})(); + Object.defineProperty(x, "z", {}); + x.z; + })(); +} diff --git a/js/src/jit-test/tests/collections/bug-1884927.js b/js/src/jit-test/tests/collections/bug-1884927.js new file mode 100644 index 0000000000..263d9df8a0 --- /dev/null +++ b/js/src/jit-test/tests/collections/bug-1884927.js @@ -0,0 +1,10 @@ +// |jit-test| --enable-symbols-as-weakmap-keys; skip-if: getBuildConfiguration("release_or_beta") +for (x=0; x<10000; ++x) { + try { + m13 = new WeakMap; + sym = Symbol(); + m13.set(sym, new Debugger); + startgc(1, ); + } catch (exc) {} +} + diff --git a/js/src/jit-test/tests/collections/bug-1885775.js b/js/src/jit-test/tests/collections/bug-1885775.js new file mode 100644 index 0000000000..bc14c6d58b --- /dev/null +++ b/js/src/jit-test/tests/collections/bug-1885775.js @@ -0,0 +1,12 @@ +// |jit-test| --enable-symbols-as-weakmap-keys; skip-if: getBuildConfiguration("release_or_beta") +var code = ` +var m58 = new WeakMap; +var sym = Symbol(); +m58.set(sym, ({ entry16: 0, length: 1 })); +function testCompacting() { + gcslice(50000); +} +testCompacting(2, 100000, 50000); +`; +for (x = 0; x < 10000; ++x) + evaluate(code); diff --git a/js/src/jit-test/tests/collections/bug-1887939-1.js b/js/src/jit-test/tests/collections/bug-1887939-1.js new file mode 100644 index 0000000000..292c44d492 --- /dev/null +++ b/js/src/jit-test/tests/collections/bug-1887939-1.js @@ -0,0 +1,7 @@ +var map = new WeakMap(); +var sym = Symbol(); +try { + map.set(sym, 1); +} catch (e) { + assertEq(!!e.message.match(/an unregistered symbol/), false); +} diff --git a/js/src/jit-test/tests/collections/bug-1887939-2.js b/js/src/jit-test/tests/collections/bug-1887939-2.js new file mode 100644 index 0000000000..2ec4e4c585 --- /dev/null +++ b/js/src/jit-test/tests/collections/bug-1887939-2.js @@ -0,0 +1,7 @@ +// |jit-test| --enable-symbols-as-weakmap-keys; skip-if: getBuildConfiguration("release_or_beta") +var map = new WeakMap(); +try { + map.set(1, 1); +} catch (e) { + assertEq(!!e.message.match(/an unregistered symbol/), true); +} diff --git a/js/src/jit-test/tests/debug/Debugger-onNativeCall-03.js b/js/src/jit-test/tests/debug/Debugger-onNativeCall-03.js index 7e9c0b280a..27ab5b2b23 100644 --- a/js/src/jit-test/tests/debug/Debugger-onNativeCall-03.js +++ b/js/src/jit-test/tests/debug/Debugger-onNativeCall-03.js @@ -1,4 +1,5 @@ -// Test onNativeCall's behavior when used with self-hosted functions. +// Test onNativeCall's behavior when used with self-hosted functions +// and trampoline natives. load(libdir + 'eqArrayHelper.js'); @@ -18,13 +19,22 @@ dbg.onNativeCall = f => { gdbg.executeInGlobal(` var x = [1,3,2]; + x.forEach((a) => {print(a)}); x.sort((a, b) => {print(a)}); + x.sort(print); `); assertEqArray(rv, [ - "EnterFrame", "sort", - "ArraySortCompare/<", + "EnterFrame", "forEach", "EnterFrame", "print", - "ArraySortCompare/<", "EnterFrame", "print", + "EnterFrame", "print", + + "sort", + "EnterFrame","print", + "EnterFrame","print", + + "sort", + "print", + "print" ]); diff --git a/js/src/jit-test/tests/debug/Environment-methods-toPrimitive.js b/js/src/jit-test/tests/debug/Environment-methods-toPrimitive.js new file mode 100644 index 0000000000..106728901d --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-methods-toPrimitive.js @@ -0,0 +1,21 @@ +// removeDebuggee can be called through ToPrimitive while converting the argument +// passed to Debugger.Environment.{find,getVariable,setVariable} to string. + +var g = newGlobal({newCompartment: true}); +g.eval("function f() { debugger; }"); +var dbg = new Debugger(); +var oddball = {[Symbol.toPrimitive]: () => dbg.removeDebuggee(g)}; + +for (var method of ["find", "getVariable", "setVariable"]) { + dbg.addDebuggee(g); + dbg.onDebuggerStatement = frame => { + var ex; + try { + frame.environment[method](oddball, oddball); + } catch (e) { + ex = e; + } + assertEq(ex.message, "Debugger.Environment is not a debuggee environment"); + }; + g.f(); +} diff --git a/js/src/jit-test/tests/debug/Frame-onStep-21.js b/js/src/jit-test/tests/debug/Frame-onStep-21.js new file mode 100644 index 0000000000..7bea2e3a95 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-21.js @@ -0,0 +1,19 @@ +// |jit-test| error: too much recursion + +// Generator closed due to over-recursion shouldn't cause crash around onStep. + +async function* foo() { + const g = this.newGlobal({sameZoneAs: this}); + g.Debugger(this).getNewestFrame().onStep = g.evaluate(`(function() {})`); + return {}; +} +function f() { + try { + f.apply(undefined, f); + } catch { + drainJobQueue(); + foo().next(); + } +} +foo().next(); +f(); diff --git a/js/src/jit-test/tests/debug/private-methods-eval-in-frame.js b/js/src/jit-test/tests/debug/private-methods-eval-in-frame.js index 5122cfa56b..318a36f614 100644 --- a/js/src/jit-test/tests/debug/private-methods-eval-in-frame.js +++ b/js/src/jit-test/tests/debug/private-methods-eval-in-frame.js @@ -150,13 +150,6 @@ if ('dis' in this) { assertEq(b.ef(`var x = () => { return this.#priv(); }; x()`), 12); assertEq(b.ef(`function x(o) { function y(o) { return o.#priv(); }; return y(o); } x(this)`), 12); -assertEq(b.ef("B.#smethod()"), 14) -assertEq(b.ef("B.#unusedmethod()"), 19); -assertEq(b.ef("B.#unusedgetter"), 10); - -b.ef("B.#unusedsetter = 19"); -assertEq(B.setter, 19); - assertEq(B.f(), 14); assertEq(B.sef(`this.#smethod()`), 14); assertEq(B.sLayerEf(`this.#smethod()`), 14); @@ -215,4 +208,4 @@ var x = () => { })(); }; x() -`), 12);
\ No newline at end of file +`), 12); diff --git a/js/src/jit-test/tests/errors/bug-1886940-2.js b/js/src/jit-test/tests/errors/bug-1886940-2.js new file mode 100644 index 0000000000..654071be04 --- /dev/null +++ b/js/src/jit-test/tests/errors/bug-1886940-2.js @@ -0,0 +1,6 @@ +oomTest(function () { + (function () { + var x = [disassemble, new Int8Array(2 ** 8 + 1)]; + x.shift().apply([], x); + })(); +}); diff --git a/js/src/jit-test/tests/errors/bug-1886940.js b/js/src/jit-test/tests/errors/bug-1886940.js new file mode 100644 index 0000000000..f8d3020d8c --- /dev/null +++ b/js/src/jit-test/tests/errors/bug-1886940.js @@ -0,0 +1,2 @@ +// |jit-test| error: RangeError +[].with(Symbol.hasInstance); diff --git a/js/src/jit-test/tests/fuses/optimized-getiterator-invalidation.js b/js/src/jit-test/tests/fuses/optimized-getiterator-invalidation.js new file mode 100644 index 0000000000..6505f8d023 --- /dev/null +++ b/js/src/jit-test/tests/fuses/optimized-getiterator-invalidation.js @@ -0,0 +1,37 @@ + +const ITERS = 1000; + +// A function which when warp compiled should use +// OptimizedGetIterator elision, and rely on invalidation +function f(x) { + let sum = 0; + for (let i = 0; i < ITERS; i++) { + const [a, b, c] = x + sum = a + b + c; + } + return sum +} + +// Run the function f 1000 times to warp compile it. Use 4 elements here to ensure +// the return property of the ArrayIteratorPrototype is called. +let arr = [1, 2, 3, 4]; +for (let i = 0; i < 1000; i++) { + f(arr); +} + +// Initialize the globally scoped counter +let counter = 0; +const ArrayIteratorPrototype = Object.getPrototypeOf([][Symbol.iterator]()); + +// Setting the return property should invalidate the warp script here. +ArrayIteratorPrototype.return = function () { + counter++; + return { done: true }; +}; + + +// Call f one more time +f(arr); + +// Use assertEq to check the value of counter. +assertEq(counter, ITERS); diff --git a/js/src/jit-test/tests/gc/alllcation-metadata-builder-over-recursion.js b/js/src/jit-test/tests/gc/alllcation-metadata-builder-over-recursion.js new file mode 100644 index 0000000000..67093026b4 --- /dev/null +++ b/js/src/jit-test/tests/gc/alllcation-metadata-builder-over-recursion.js @@ -0,0 +1,22 @@ +// |jit-test| allow-unhandlable-oom + +// Over-recursion should suppress alloation metadata builder, to avoid another +// over-recursion while generating an error object for the first over-recursion. +// +// This test should catch the error for the "load" testing function's arguments, +// or crash with unhandlable OOM inside allocation metadata builder. + +const g = newGlobal(); +g.enableShellAllocationMetadataBuilder(); +function run() { + const g_load = g.load; + g_load.toString = run; + return g_load(g_load); +} +let caught = false; +try { + run(); +} catch (e) { + caught = true; +} +assertEq(caught, true); diff --git a/js/src/jit-test/tests/gc/bug-1568740.js b/js/src/jit-test/tests/gc/bug-1568740.js index 6cc003cb94..5c311b855d 100644 --- a/js/src/jit-test/tests/gc/bug-1568740.js +++ b/js/src/jit-test/tests/gc/bug-1568740.js @@ -1,11 +1,11 @@ gczeal(0); +gcparam("semispaceNurseryEnabled", 0); function setAndTest(param, value) { gcparam(param, value); assertEq(gcparam(param), value); } - // Set a large nursery size. setAndTest("maxNurseryBytes", 1024*1024); setAndTest("minNurseryBytes", 1024*1024); diff --git a/js/src/jit-test/tests/gc/bug-1569840.js b/js/src/jit-test/tests/gc/bug-1569840.js index 70d28add73..45df339405 100644 --- a/js/src/jit-test/tests/gc/bug-1569840.js +++ b/js/src/jit-test/tests/gc/bug-1569840.js @@ -1,5 +1,5 @@ - gczeal(0); +gcparam("semispaceNurseryEnabled", 0); gcparam("maxNurseryBytes", 1024*1024); gcparam("minNurseryBytes", 1024*1024); diff --git a/js/src/jit-test/tests/gc/bug-1885819-2.js b/js/src/jit-test/tests/gc/bug-1885819-2.js new file mode 100644 index 0000000000..a87e4c701a --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1885819-2.js @@ -0,0 +1,12 @@ +let g = newGlobal(); +function f() { + var o = {}; + o["prop" + Date.now()] = 1; + gc(); + schedulezone("atoms"); + schedulezone(g); + gc("zone"); + let [x] = [0]; +} +f(); +oomTest(f); diff --git a/js/src/jit-test/tests/gc/bug-1885819.js b/js/src/jit-test/tests/gc/bug-1885819.js new file mode 100644 index 0000000000..8341c3ff52 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1885819.js @@ -0,0 +1,10 @@ +function f() { + var o = {}; + o["prop" + Date.now()] = 1; + gc(); + schedulezone("atoms"); + gc("zone"); + let [x] = [0]; +} +f(); +oomTest(f); diff --git a/js/src/jit-test/tests/gc/bug-1886466.js b/js/src/jit-test/tests/gc/bug-1886466.js new file mode 100644 index 0000000000..4347ea3e6b --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1886466.js @@ -0,0 +1,5 @@ +gczeal(7, 6) +a = new WeakSet +for (let i = 0; i < 200000; i++) { + a.add({}) +} diff --git a/js/src/jit-test/tests/gc/bug-1888717.js b/js/src/jit-test/tests/gc/bug-1888717.js new file mode 100644 index 0000000000..7e54543994 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1888717.js @@ -0,0 +1,3 @@ +// |jit-test| --no-ggc +gcparam("semispaceNurseryEnabled", 1); +let o = {}; diff --git a/js/src/jit-test/tests/gc/dedupe-03.js b/js/src/jit-test/tests/gc/dedupe-03.js new file mode 100644 index 0000000000..4e9b4c1bbc --- /dev/null +++ b/js/src/jit-test/tests/gc/dedupe-03.js @@ -0,0 +1,66 @@ +// |jit-test| skip-if: !hasFunction.stringRepresentation + +// Test handling of tenured dependent strings pointing to nursery base strings. + +gczeal(0); + +function makeExtensibleStrFrom(str) { + var left = str.substr(0, str.length/2); + var right = str.substr(str.length/2, str.length); + var ropeStr = left + right; + return ensureLinearString(ropeStr); +} + +function repr(s) { + return JSON.parse(stringRepresentation(s)); +} + +function dependsOn(s1, s2) { + const rep1 = JSON.parse(stringRepresentation(s1)); + const rep2 = JSON.parse(stringRepresentation(s2)); + return rep1.base && rep1.base.address == rep2.address; +} + +// Make a string to deduplicate to. +var original = makeExtensibleStrFrom('abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm'); + +// Construct T1 -> Nbase. +var Nbase = makeExtensibleStrFrom('abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm'); +var T1 = newDependentString(Nbase, 0, 60, { tenured: true }); + +// Get prevented from creating T2 -> T1 -> Nbase +// (will be T2 -> Nbase instead to avoid dependency chains). +var T2 = newDependentString(T1, 30, { tenured: true }); + +assertEq(dependsOn(T2, Nbase), "expect: T2 -> base"); + +// Construct T1 -> Ndep1 (was Nbase) -> Nbase2. +var Nbase2 = newRope(Nbase, "ABC"); +ensureLinearString(Nbase2); +var Ndep1 = Nbase; + +assertEq(dependsOn(T1, Ndep1), "expect: T1 -> Ndep1"); +assertEq(dependsOn(Ndep1, Nbase2), "expect: Ndep1 -> Nbase2"); + +// Fail to construct T3 -> Tbase3 -> Nbase4. It will refuse because T3 would be using +// chars from Nbase4 that can't be updated since T3 is not in the store buffer. Instead, +// it will allocate a new buffer for the rope root, leaving Tbase3 alone and keeping +// T3 -> Tbase3. +var Tbase3 = makeExtensibleStrFrom('abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm'); +minorgc(); +var T3 = newDependentString(Tbase3, 0, 30, { tenured: true }); +var Nbase4 = newRope(Tbase3, "DEF"); +ensureLinearString(Nbase4); +assertEq(repr(Tbase3).isTenured, true, "Tbase3 is tenured"); +assertEq(repr(Tbase3).flags.includes("EXTENSIBLE"), true, "Tbase3 is extensible"); +assertEq(repr(Nbase4).flags.includes("DEPENDENT_BIT"), false, "expect: Nbase4 is not a dependent string") +assertEq(repr(T3).flags.includes("DEPENDENT_BIT"), true, "expect: T3 is a dependent string") +assertEq(dependsOn(T3, Tbase3), "expect: T3 -> Tbase3"); + +function bug1879918() { + const s = JSON.parse('["abcdefabcdefabcdefabcdefabcdefabcdefabcdef"]')[0]; + const dep = newDependentString(s, 1, { tenured: true }); + minorgc(); + assertEq(dep, "bcdefabcdefabcdefabcdefabcdefabcdefabcdef"); +} +bug1879918(); diff --git a/js/src/jit-test/tests/gc/deduplicateTenuringStrings.js b/js/src/jit-test/tests/gc/deduplicateTenuringStrings.js index 1b8259cc15..de2fb0c028 100644 --- a/js/src/jit-test/tests/gc/deduplicateTenuringStrings.js +++ b/js/src/jit-test/tests/gc/deduplicateTenuringStrings.js @@ -13,6 +13,7 @@ // We require predictable GC timing to make sure the correct // strings are tenured together. gczeal(0); +gcparam('semispaceNurseryEnabled', 0); var helperCode = ` function makeInlineStr(isLatin1) { diff --git a/js/src/jit-test/tests/gc/gcparam.js b/js/src/jit-test/tests/gc/gcparam.js index 05e0359088..c57b400642 100644 --- a/js/src/jit-test/tests/gc/gcparam.js +++ b/js/src/jit-test/tests/gc/gcparam.js @@ -60,3 +60,4 @@ testChangeParam("mallocThresholdBase"); testChangeParam("urgentThreshold"); testChangeParam("helperThreadRatio"); testChangeParam("maxHelperThreads"); +testChangeParam("semispaceNurseryEnabled"); diff --git a/js/src/jit-test/tests/gc/pretenuring.js b/js/src/jit-test/tests/gc/pretenuring.js index 6f20706e9b..30156b0e98 100644 --- a/js/src/jit-test/tests/gc/pretenuring.js +++ b/js/src/jit-test/tests/gc/pretenuring.js @@ -1,6 +1,7 @@ // Test nursery string allocation and pretenuring. gczeal(0); +gcparam("semispaceNurseryEnabled", 0); gcparam("minNurseryBytes", 4096 * 1024); gcparam("maxNurseryBytes", 4096 * 1024); diff --git a/js/src/jit-test/tests/heap-analysis/byteSize-of-string.js b/js/src/jit-test/tests/heap-analysis/byteSize-of-string.js index aaac0c4f1f..b4cfdffb04 100644 --- a/js/src/jit-test/tests/heap-analysis/byteSize-of-string.js +++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-string.js @@ -12,6 +12,7 @@ // stable. gczeal(0); // Need to control when tenuring happens +gcparam('semispaceNurseryEnabled', 0); // Hack to skip this test if strings are not allocated in the nursery. { @@ -75,16 +76,20 @@ function tByteSize(str) { // JSExternalString - limited by MaxStringLength - E // JSThinInlineString 8 4 16 8 T // JSFatInlineString 24 12 24 12 F +// ThinInlineAtom 12 6 20 10 T +// FatInlineAtom 20 10 20 10 F // JSExtensibleString - limited by MaxStringLength - X // Notes: // - labels are suffixed with A for atoms and N for non-atoms -// - atoms are 8 bytes larger than non-atoms, to store the atom's hash code. +// - atoms store a 4 byte hash code, and some add to the size to adjust // - Nursery-allocated strings require a header that stores the zone. // Expected sizes based on type of string const m32 = (getBuildConfiguration("pointer-byte-size") == 4); -const TA = m32 ? 24 : 32; // ThinInlineString atom, includes a hash value +const TA = m32 ? 24 : 32; // ThinInlineAtom (includes a hash value) +const FA = m32 ? 32 : 32; // FatInlineAtom (includes a hash value) +const NA = m32 ? 24 : 32; // NormalAtom const TN = m32 ? 16 : 24; // ThinInlineString const FN = m32 ? 32 : 32; // FatInlineString const XN = m32 ? 16 : 24; // ExtensibleString, has additional storage buffer @@ -95,8 +100,8 @@ const EN = m32 ? 16 : 24; // ExternalString // A function that pads out a tenured size to the nursery size. We store a zone // pointer in the nursery just before the string (4 bytes on 32-bit, 8 bytes on // 64-bit), and the string struct itself must be 8-byte aligned (resulting in -// +4 bytes on 32-bit, +0 bytes on 64-bit). The end result? Nursery strings are -// 8 bytes larger. +// +4 bytes on 32-bit, +0 bytes on 64-bit). The end result is that nursery +// strings are 8 bytes larger. const Nursery = m32 ? s => s + 4 + 4 : s => s + 8 + 0; // Latin-1 @@ -130,6 +135,23 @@ assertEq(nByteSize("123456789.123456789.123456789.1"), s(Nursery( assertEq(nByteSize("123456789.123456789.123456789.12"), s(Nursery(XN)+32,Nursery(XN)+32)); assertEq(nByteSize("123456789.123456789.123456789.123"), s(Nursery(XN)+64,Nursery(XN)+64)); +function Atom(s) { return Object.keys({ [s]: true })[0]; } +assertEq(byteSize(Atom("1234567")), s(TA, TA)); +assertEq(byteSize(Atom("12345678")), s(TA, FA)); +assertEq(byteSize(Atom("123456789.12")), s(TA, FA)); +assertEq(byteSize(Atom("123456789.123")), s(FA, FA)); +assertEq(byteSize(Atom("123456789.12345")), s(FA, FA)); +assertEq(byteSize(Atom("123456789.123456")), s(FA, FA)); +assertEq(byteSize(Atom("123456789.1234567")), s(FA, FA)); +assertEq(byteSize(Atom("123456789.123456789.")), s(FA, FA)); +assertEq(byteSize(Atom("123456789.123456789.1")), s(NA+32, NA+32)); +assertEq(byteSize(Atom("123456789.123456789.123")), s(NA+32, NA+32)); +assertEq(byteSize(Atom("123456789.123456789.1234")), s(NA+32, NA+32)); +assertEq(byteSize(Atom("123456789.123456789.12345")), s(NA+32, NA+32)); +assertEq(byteSize(Atom("123456789.123456789.123456789.1")), s(NA+32, NA+32)); +assertEq(byteSize(Atom("123456789.123456789.123456789.12")), s(NA+32, NA+32)); +assertEq(byteSize(Atom("123456789.123456789.123456789.123")), s(NA+48, NA+48)); + // Inline char16_t atoms. // "Impassionate gods have never seen the red that is the Tatsuta River." // - Ariwara no Narihira @@ -183,20 +205,43 @@ assertEq(byteSize(rope8), s(Nurser minorgc(); assertEq(byteSize(rope8), s(RN, RN)); var matches8 = rope8.match(/(de cuyo nombre no quiero acordarme)/); -assertEq(byteSize(rope8), s(XN + 65536, XN + 65536)); +assertEq(byteSize(rope8), s(XN + 64 * 1024, XN + 64 * 1024)); +var ext8 = rope8; // Stop calling it what it's not (though it'll change again soon.) // Test extensible strings. // // Appending another copy of the fragment should yield another rope. // -// Flatting that should turn the original rope into a dependent string, and +// Flattening that should turn the original rope into a dependent string, and // yield a new linear string, of the same size as the original. -rope8a = rope8 + fragment8; +var rope8a = ext8 + fragment8; assertEq(byteSize(rope8a), s(Nursery(RN), Nursery(RN))); rope8a.match(/x/, function() { assertEq(true, false); }); assertEq(byteSize(rope8a), s(Nursery(XN) + 65536, Nursery(XN) + 65536)); +assertEq(byteSize(ext8), s(DN, DN)); + +// Latin-1 dependent strings in the nursery. +assertEq(byteSize(ext8.substr(1000, 2000)), s(Nursery(DN), Nursery(DN))); +assertEq(byteSize(matches8[0]), s(Nursery(DN), Nursery(DN))); +assertEq(byteSize(matches8[1]), s(Nursery(DN), Nursery(DN))); + +// Tenure everything and do it again. +ext8 = copyString(ext8); +rope8a = ext8 + fragment8; +minorgc(); +assertEq(byteSize(rope8a), s(RN, RN)); +rope8a.match(/x/, function() { assertEq(true, false); }); +assertEq(byteSize(rope8a), s(XN + 65536, XN + 65536)); assertEq(byteSize(rope8), s(RN, RN)); +// Latin-1 tenured dependent strings. +function tenure(s) { + minorgc(); + return s; +} +assertEq(byteSize(tenure(rope8.substr(1000, 2000))), s(DN, DN)); +assertEq(byteSize(matches8[0]), s(DN, DN)); +assertEq(byteSize(matches8[1]), s(DN, DN)); // A char16_t rope. This changes size when flattened. // "From the Heliconian Muses let us begin to sing" @@ -207,13 +252,11 @@ for (var i = 0; i < 10; i++) // 1024 repetitions rope16 = rope16 + rope16; assertEq(byteSize(rope16), s(Nursery(RN), Nursery(RN))); let matches16 = rope16.match(/(Ἑλικωνιάδων ἀρχώμεθ᾽)/); -assertEq(byteSize(rope16), s(Nursery(RN) + 131072, Nursery(RN) + 131072)); +assertEq(byteSize(rope16), s(Nursery(XN) + 128 * 1024, Nursery(XN) + 128 * 1024)); +var ext16 = rope16; -// Latin-1 and char16_t dependent strings. -assertEq(byteSize(rope8.substr(1000, 2000)), s(Nursery(DN), Nursery(DN))); -assertEq(byteSize(rope16.substr(1000, 2000)), s(Nursery(DN), Nursery(DN))); -assertEq(byteSize(matches8[0]), s(Nursery(DN), Nursery(DN))); -assertEq(byteSize(matches8[1]), s(Nursery(DN), Nursery(DN))); +// char16_t dependent strings in the nursery. +assertEq(byteSize(ext16.substr(1000, 2000)), s(Nursery(DN), Nursery(DN))); assertEq(byteSize(matches16[0]), s(Nursery(DN), Nursery(DN))); assertEq(byteSize(matches16[1]), s(Nursery(DN), Nursery(DN))); @@ -221,13 +264,23 @@ assertEq(byteSize(matches16[1]), s(Nurser // // Appending another copy of the fragment should yield another rope. // -// Flatting that should turn the original rope into a dependent string, and +// Flattening that should turn the original rope into a dependent string, and // yield a new linear string, of the some size as the original. -rope16a = rope16 + fragment16; +rope16a = ext16 + fragment16; assertEq(byteSize(rope16a), s(Nursery(RN), Nursery(RN))); rope16a.match(/x/, function() { assertEq(true, false); }); -assertEq(byteSize(rope16a), s(Nursery(XN) + 131072, Nursery(XN) + 131072)); -assertEq(byteSize(rope16), s(Nursery(XN), Nursery(XN))); +assertEq(byteSize(rope16a), s(Nursery(XN) + 128 * 1024, Nursery(XN) + 128 * 1024)); +assertEq(byteSize(ext16), s(Nursery(DN), Nursery(DN))); + +// Tenure everything and try again. This time it should steal the extensible +// characters and convert the root into an extensible string using them. +ext16 = copyString(ext16); +rope16a = ext16 + fragment16; +minorgc(); +assertEq(byteSize(rope16a), s(RN, RN)); +rope16a.match(/x/, function() { assertEq(true, false); }); +assertEq(byteSize(rope16a), s(XN + 128 * 1024, XN + 128 * 1024)); +assertEq(byteSize(ext16), s(RN, RN)); // Test external strings. // diff --git a/js/src/jit-test/tests/ion/apply-native-arguments-object.js b/js/src/jit-test/tests/ion/apply-native-arguments-object.js new file mode 100644 index 0000000000..e06a5e0965 --- /dev/null +++ b/js/src/jit-test/tests/ion/apply-native-arguments-object.js @@ -0,0 +1,46 @@ +load(libdir + "array-compare.js"); + +const xs = [ + // Zero arguments. + [], + + // Single argument. + [1], + + // Few arguments. Even number of arguments. + [1, 2], + + // Few arguments. Odd number of arguments. + [1, 2, 3], + + // Many arguments. Even number of arguments. + [1, 2, 3, 4, 5, 6, 7, 8, 9, 0], + + // Many arguments. Odd number of arguments. + [1, 2, 3, 4, 5, 6, 7, 8, 9], +]; + +function escape() { + with ({}) ; +} + +function f() { + // Let |arguments| escape to force the allocation of an arguments object. + escape(arguments); + + // FunApply to a native function with an arguments object. + return Array.apply(null, arguments); +} + +// Don't inline |f| into the top-level script. +with ({}) ; + +for (let i = 0; i < 400; ++i) { + let x = xs[i % xs.length]; + + // NB: Array(1) creates the array `[,]`. + let expected = x.length !== 1 ? x : [,]; + + let result = f.apply(null, x); + assertEq(arraysEqual(result, expected), true); +} diff --git a/js/src/jit-test/tests/ion/apply-native-arguments.js b/js/src/jit-test/tests/ion/apply-native-arguments.js new file mode 100644 index 0000000000..3d6729ca76 --- /dev/null +++ b/js/src/jit-test/tests/ion/apply-native-arguments.js @@ -0,0 +1,39 @@ +load(libdir + "array-compare.js"); + +const xs = [ + // Zero arguments. + [], + + // Single argument. + [1], + + // Few arguments. Even number of arguments. + [1, 2], + + // Few arguments. Odd number of arguments. + [1, 2, 3], + + // Many arguments. Even number of arguments. + [1, 2, 3, 4, 5, 6, 7, 8, 9, 0], + + // Many arguments. Odd number of arguments. + [1, 2, 3, 4, 5, 6, 7, 8, 9], +]; + +function f() { + // FunApply to a native function with frame arguments. + return Array.apply(null, arguments); +} + +// Don't inline |f| into the top-level script. +with ({}) ; + +for (let i = 0; i < 400; ++i) { + let x = xs[i % xs.length]; + + // NB: Array(1) creates the array `[,]`. + let expected = x.length !== 1 ? x : [,]; + + let result = f.apply(null, x); + assertEq(arraysEqual(result, expected), true); +} diff --git a/js/src/jit-test/tests/ion/apply-native-array.js b/js/src/jit-test/tests/ion/apply-native-array.js new file mode 100644 index 0000000000..0dfa2df947 --- /dev/null +++ b/js/src/jit-test/tests/ion/apply-native-array.js @@ -0,0 +1,39 @@ +load(libdir + "array-compare.js"); + +const xs = [ + // Zero arguments. + [], + + // Single argument. + [1], + + // Few arguments. Even number of arguments. + [1, 2], + + // Few arguments. Odd number of arguments. + [1, 2, 3], + + // Many arguments. Even number of arguments. + [1, 2, 3, 4, 5, 6, 7, 8, 9, 0], + + // Many arguments. Odd number of arguments. + [1, 2, 3, 4, 5, 6, 7, 8, 9], +]; + +function f(x) { + // FunApply to a native function with an array. + return Array.apply(null, x); +} + +// Don't inline |f| into the top-level script. +with ({}) ; + +for (let i = 0; i < 400; ++i) { + let x = xs[i % xs.length]; + + // NB: Array(1) creates the array `[,]`. + let expected = x.length !== 1 ? x : [,]; + + let result = f(x); + assertEq(arraysEqual(result, expected), true); +} diff --git a/js/src/jit-test/tests/ion/apply-native-spreadcall-arguments.js b/js/src/jit-test/tests/ion/apply-native-spreadcall-arguments.js new file mode 100644 index 0000000000..9f769e4a59 --- /dev/null +++ b/js/src/jit-test/tests/ion/apply-native-spreadcall-arguments.js @@ -0,0 +1,39 @@ +load(libdir + "array-compare.js"); + +const xs = [ + // Zero arguments. + [], + + // Single argument. + [1], + + // Few arguments. Even number of arguments. + [1, 2], + + // Few arguments. Odd number of arguments. + [1, 2, 3], + + // Many arguments. Even number of arguments. + [1, 2, 3, 4, 5, 6, 7, 8, 9, 0], + + // Many arguments. Odd number of arguments. + [1, 2, 3, 4, 5, 6, 7, 8, 9], +]; + +function f() { + // SpreadCall to a native function with frame arguments. + return Array(...arguments); +} + +// Don't inline |f| into the top-level script. +with ({}) ; + +for (let i = 0; i < 400; ++i) { + let x = xs[i % xs.length]; + + // NB: Array(1) creates the array `[,]`. + let expected = x.length !== 1 ? x : [,]; + + let result = f.apply(null, x); + assertEq(arraysEqual(result, expected), true); +} diff --git a/js/src/jit-test/tests/ion/apply-native-spreadcall-array.js b/js/src/jit-test/tests/ion/apply-native-spreadcall-array.js new file mode 100644 index 0000000000..24e5621484 --- /dev/null +++ b/js/src/jit-test/tests/ion/apply-native-spreadcall-array.js @@ -0,0 +1,39 @@ +load(libdir + "array-compare.js"); + +const xs = [ + // Zero arguments. + [], + + // Single argument. + [1], + + // Few arguments. Even number of arguments. + [1, 2], + + // Few arguments. Odd number of arguments. + [1, 2, 3], + + // Many arguments. Even number of arguments. + [1, 2, 3, 4, 5, 6, 7, 8, 9, 0], + + // Many arguments. Odd number of arguments. + [1, 2, 3, 4, 5, 6, 7, 8, 9], +]; + +function f(x) { + // SpreadCall to a native function with an array. + return Array(...x); +} + +// Don't inline |f| into the top-level script. +with ({}) ; + +for (let i = 0; i < 400; ++i) { + let x = xs[i % xs.length]; + + // NB: Array(1) creates the array `[,]`. + let expected = x.length !== 1 ? x : [,]; + + let result = f(x); + assertEq(arraysEqual(result, expected), true); +} diff --git a/js/src/jit-test/tests/ion/apply-native-spreadcall-rest.js b/js/src/jit-test/tests/ion/apply-native-spreadcall-rest.js new file mode 100644 index 0000000000..ba7038244d --- /dev/null +++ b/js/src/jit-test/tests/ion/apply-native-spreadcall-rest.js @@ -0,0 +1,39 @@ +load(libdir + "array-compare.js"); + +const xs = [ + // Zero arguments. + [], + + // Single argument. + [1], + + // Few arguments. Even number of arguments. + [1, 2], + + // Few arguments. Odd number of arguments. + [1, 2, 3], + + // Many arguments. Even number of arguments. + [1, 2, 3, 4, 5, 6, 7, 8, 9, 0], + + // Many arguments. Odd number of arguments. + [1, 2, 3, 4, 5, 6, 7, 8, 9], +]; + +function f(...x) { + // SpreadCall to a native function with rest-args. + return Array(...x); +} + +// Don't inline |f| into the top-level script. +with ({}) ; + +for (let i = 0; i < 400; ++i) { + let x = xs[i % xs.length]; + + // NB: Array(1) creates the array `[,]`. + let expected = x.length !== 1 ? x : [,]; + + let result = f.apply(null, x); + assertEq(arraysEqual(result, expected), true); +} diff --git a/js/src/jit-test/tests/ion/apply-native-spreadnew-arguments.js b/js/src/jit-test/tests/ion/apply-native-spreadnew-arguments.js new file mode 100644 index 0000000000..7e31cdcbd6 --- /dev/null +++ b/js/src/jit-test/tests/ion/apply-native-spreadnew-arguments.js @@ -0,0 +1,39 @@ +load(libdir + "array-compare.js"); + +const xs = [ + // Zero arguments. + [], + + // Single argument. + [1], + + // Few arguments. Even number of arguments. + [1, 2], + + // Few arguments. Odd number of arguments. + [1, 2, 3], + + // Many arguments. Even number of arguments. + [1, 2, 3, 4, 5, 6, 7, 8, 9, 0], + + // Many arguments. Odd number of arguments. + [1, 2, 3, 4, 5, 6, 7, 8, 9], +]; + +function f() { + // SpreadNew to a native function with frame arguments. + return new Array(...arguments); +} + +// Don't inline |f| into the top-level script. +with ({}) ; + +for (let i = 0; i < 400; ++i) { + let x = xs[i % xs.length]; + + // NB: Array(1) creates the array `[,]`. + let expected = x.length !== 1 ? x : [,]; + + let result = f.apply(null, x); + assertEq(arraysEqual(result, expected), true); +} diff --git a/js/src/jit-test/tests/ion/apply-native-spreadnew-array.js b/js/src/jit-test/tests/ion/apply-native-spreadnew-array.js new file mode 100644 index 0000000000..5c716f48b4 --- /dev/null +++ b/js/src/jit-test/tests/ion/apply-native-spreadnew-array.js @@ -0,0 +1,39 @@ +load(libdir + "array-compare.js"); + +const xs = [ + // Zero arguments. + [], + + // Single argument. + [1], + + // Few arguments. Even number of arguments. + [1, 2], + + // Few arguments. Odd number of arguments. + [1, 2, 3], + + // Many arguments. Even number of arguments. + [1, 2, 3, 4, 5, 6, 7, 8, 9, 0], + + // Many arguments. Odd number of arguments. + [1, 2, 3, 4, 5, 6, 7, 8, 9], +]; + +function f(x) { + // SpreadNew to a native function with an array. + return new Array(...x); +} + +// Don't inline |f| into the top-level script. +with ({}) ; + +for (let i = 0; i < 400; ++i) { + let x = xs[i % xs.length]; + + // NB: Array(1) creates the array `[,]`. + let expected = x.length !== 1 ? x : [,]; + + let result = f(x); + assertEq(arraysEqual(result, expected), true); +} diff --git a/js/src/jit-test/tests/ion/apply-native-spreadnew-newtarget.js b/js/src/jit-test/tests/ion/apply-native-spreadnew-newtarget.js new file mode 100644 index 0000000000..9ffe53277b --- /dev/null +++ b/js/src/jit-test/tests/ion/apply-native-spreadnew-newtarget.js @@ -0,0 +1,66 @@ +load(libdir + "array-compare.js"); + +const xs = [ + // Zero arguments. + [], + + // Single argument. + [1], + + // Few arguments. Even number of arguments. + [1, 2], + + // Few arguments. Odd number of arguments. + [1, 2, 3], + + // Many arguments. Even number of arguments. + [1, 2, 3, 4, 5, 6, 7, 8, 9, 0], + + // Many arguments. Odd number of arguments. + [1, 2, 3, 4, 5, 6, 7, 8, 9], +]; + +class ArrayWithExplicitConstructor extends Array { + constructor(...args) { + super(...args); + } +} + +class ArrayWithImplicitConstructor extends Array { + constructor(...args) { + super(...args); + } +} + +function f(...x) { + return new ArrayWithExplicitConstructor(...x); +} + +function g(...x) { + return new ArrayWithImplicitConstructor(...x); +} + +// Don't inline |f| and |g| into the top-level script. +with ({}) ; + +for (let i = 0; i < 400; ++i) { + let x = xs[i % xs.length]; + + // NB: Array(1) creates the array `[,]`. + let expected = x.length !== 1 ? x : [,]; + + let result = f.apply(null, x); + assertEq(arraysEqual(result, expected), true); + assertEq(Object.getPrototypeOf(result), ArrayWithExplicitConstructor.prototype); +} + +for (let i = 0; i < 400; ++i) { + let x = xs[i % xs.length]; + + // NB: Array(1) creates the array `[,]`. + let expected = x.length !== 1 ? x : [,]; + + let result = g.apply(null, x); + assertEq(arraysEqual(result, expected), true); + assertEq(Object.getPrototypeOf(result), ArrayWithImplicitConstructor.prototype); +} diff --git a/js/src/jit-test/tests/ion/apply-native-spreadnew-rest.js b/js/src/jit-test/tests/ion/apply-native-spreadnew-rest.js new file mode 100644 index 0000000000..58de8fa239 --- /dev/null +++ b/js/src/jit-test/tests/ion/apply-native-spreadnew-rest.js @@ -0,0 +1,39 @@ +load(libdir + "array-compare.js"); + +const xs = [ + // Zero arguments. + [], + + // Single argument. + [1], + + // Few arguments. Even number of arguments. + [1, 2], + + // Few arguments. Odd number of arguments. + [1, 2, 3], + + // Many arguments. Even number of arguments. + [1, 2, 3, 4, 5, 6, 7, 8, 9, 0], + + // Many arguments. Odd number of arguments. + [1, 2, 3, 4, 5, 6, 7, 8, 9], +]; + +function f(...x) { + // SpreadNew to a native function with rest-args. + return new Array(...x); +} + +// Don't inline |f| into the top-level script. +with ({}) ; + +for (let i = 0; i < 400; ++i) { + let x = xs[i % xs.length]; + + // NB: Array(1) creates the array `[,]`. + let expected = x.length !== 1 ? x : [,]; + + let result = f.apply(null, x); + assertEq(arraysEqual(result, expected), true); +} diff --git a/js/src/jit-test/tests/ion/recover-atomics-islockfree.js b/js/src/jit-test/tests/ion/recover-atomics-islockfree.js new file mode 100644 index 0000000000..2a57afd49b --- /dev/null +++ b/js/src/jit-test/tests/ion/recover-atomics-islockfree.js @@ -0,0 +1,25 @@ +// |jit-test| --fast-warmup; --ion-offthread-compile=off + +function foo(n, trigger) { + let result = Atomics.isLockFree(n * -1); + if (trigger) { + assertEq(result, false); + } +} + +for (var i = 0; i < 100; i++) { + foo(-50, false); +} +foo(0, true); + +function bar(n, trigger) { + let result = Atomics.isLockFree(n * 4); + if (trigger) { + assertEq(result, false); + } +} + +for (var i = 0; i < 100; i++) { + bar(1, false); +} +bar(0x40000001, true); diff --git a/js/src/jit-test/tests/ion/recover-string-from-charcode.js b/js/src/jit-test/tests/ion/recover-string-from-charcode.js new file mode 100644 index 0000000000..be060be8e7 --- /dev/null +++ b/js/src/jit-test/tests/ion/recover-string-from-charcode.js @@ -0,0 +1,13 @@ +// |jit-test| --fast-warmup; --ion-offthread-compile=off + +function foo(n, trigger) { + let result = String.fromCharCode(n * -1); + if (trigger) { + assertEq(result, "\0"); + } +} + +for (var i = 0; i < 100; i++) { + foo(-50, false); +} +foo(0, true); diff --git a/js/src/jit-test/tests/modules/bug-1888902.js b/js/src/jit-test/tests/modules/bug-1888902.js new file mode 100644 index 0000000000..7804bef98a --- /dev/null +++ b/js/src/jit-test/tests/modules/bug-1888902.js @@ -0,0 +1,16 @@ +// |jit-test| error:Error + +const v0 = ` + function F1() { + const v11 = registerModule("module1", parseModule(\`import {} from "module2"; + import {} from "module3";\`)); + const v13 = "await 1;"; + drainJobQueue(); + registerModule("module2", parseModule(v13)); + registerModule("module3", parseModule(v0)); + moduleLink(v11); + moduleEvaluate(v11); + } + F1(); +`; +eval(v0); diff --git a/js/src/jit-test/tests/modules/dynamic-import-error.js b/js/src/jit-test/tests/modules/dynamic-import-error.js index 98a6af75d0..56713c8485 100644 --- a/js/src/jit-test/tests/modules/dynamic-import-error.js +++ b/js/src/jit-test/tests/modules/dynamic-import-error.js @@ -1,5 +1,3 @@ -// |jit-test| module - let result = null; let error = null; let promise = import("nonexistent.js"); diff --git a/js/src/jit-test/tests/modules/dynamic-import-module.js b/js/src/jit-test/tests/modules/dynamic-import-module.js index 3c004258a3..fa19b74303 100644 --- a/js/src/jit-test/tests/modules/dynamic-import-module.js +++ b/js/src/jit-test/tests/modules/dynamic-import-module.js @@ -1,5 +1,3 @@ -// |jit-test| module - function testImport(path, name, value) { let result = null; let error = null; diff --git a/js/src/jit-test/tests/modules/inline-data-2.js b/js/src/jit-test/tests/modules/inline-data-2.js new file mode 100644 index 0000000000..8dbf92574d --- /dev/null +++ b/js/src/jit-test/tests/modules/inline-data-2.js @@ -0,0 +1,12 @@ +let result = null; +let error = null; +let promise = import("javascript: export let b = 100;"); +promise.then((ns) => { + result = ns; +}).catch((e) => { + error = e; +}); + +drainJobQueue(); +assertEq(error, null); +assertEq(result.b, 100); diff --git a/js/src/jit-test/tests/modules/inline-data.js b/js/src/jit-test/tests/modules/inline-data.js index 9c56856f8d..d81da0efe4 100644 --- a/js/src/jit-test/tests/modules/inline-data.js +++ b/js/src/jit-test/tests/modules/inline-data.js @@ -2,16 +2,3 @@ import { a } from "javascript: export let a = 42;"; assertEq(a, 42); - -let result = null; -let error = null; -let promise = import("javascript: export let b = 100;"); -promise.then((ns) => { - result = ns; -}).catch((e) => { - error = e; -}); - -drainJobQueue(); -assertEq(error, null); -assertEq(result.b, 100); diff --git a/js/src/jit-test/tests/modules/shell-wrapper.js b/js/src/jit-test/tests/modules/shell-wrapper.js index 1be1c486c6..058e574d4e 100644 --- a/js/src/jit-test/tests/modules/shell-wrapper.js +++ b/js/src/jit-test/tests/modules/shell-wrapper.js @@ -1,4 +1,3 @@ -// |jit-test| module // Test shell ModuleObject wrapper's accessors and methods load(libdir + "asserts.js"); @@ -49,10 +48,8 @@ const d = registerModule('d', parseModule(` f(); `)); moduleLink(d); -try { - await moduleEvaluate(d); -} catch (e) { -} +moduleEvaluate(d).catch(e => undefined); +drainJobQueue(); assertEq(d.evaluationError instanceof ReferenceError, true); testGetter(d, "evaluationError"); diff --git a/js/src/jit-test/tests/parser/bug1887176.js b/js/src/jit-test/tests/parser/bug1887176.js new file mode 100644 index 0000000000..bea2db519b --- /dev/null +++ b/js/src/jit-test/tests/parser/bug1887176.js @@ -0,0 +1,46 @@ + +// This tests a case where TokenStreamAnyChars::fillExceptingContext +// mishandled a wasm frame, leading to an assertion failure. + +if (!wasmIsSupported()) + quit(); + +const v0 = ` + const o6 = { + f() { + function F2() { + if (!new.target) { throw 'must be called with new'; } + } + return F2(); + return {}; // This can be anything, but it must be present + }, + }; + + const o7 = { + "main": o6, + }; + + const v15 = new WebAssembly.Module(wasmTextToBinary(\` + (module + (import "main" "f" (func)) + (func (export "go") + call 0 + ) + )\`)); + const v16 = new WebAssembly.Instance(v15, o7); + v16.exports.go(); +`; + +const o27 = { + // Both "fileName" and null are necessary + "fileName": null, +}; + +let caught = false; +try { + evaluate(v0, o27); +} catch (e) { + assertEq(e, "must be called with new"); + caught = true; +} +assertEq(caught, true); diff --git a/js/src/jit-test/tests/parser/dumpStencil-02.js b/js/src/jit-test/tests/parser/dumpStencil-02.js new file mode 100644 index 0000000000..e21962c36b --- /dev/null +++ b/js/src/jit-test/tests/parser/dumpStencil-02.js @@ -0,0 +1,8 @@ +let caught = false; +try { + dumpStencil("export var z;", { module : true, lineNumber: 0 }); +} catch (e) { + caught = true; + assertEq(e.message.includes("Module cannot be compiled with lineNumber == 0"), true); +} +assertEq(caught, true); diff --git a/js/src/jit-test/tests/parser/module-filename.js b/js/src/jit-test/tests/parser/module-filename.js new file mode 100644 index 0000000000..59017dd674 --- /dev/null +++ b/js/src/jit-test/tests/parser/module-filename.js @@ -0,0 +1,13 @@ +load(libdir + "asserts.js"); + +compileToStencil("", { fileName: "", module: true }); +assertThrowsInstanceOf(() => { + compileToStencil("", { fileName: null, module: true }); +}, Error); + +if (helperThreadCount() > 0) { + offThreadCompileModuleToStencil("", { fileName: "", module: true }); + assertThrowsInstanceOf(() => { + offThreadCompileModuleToStencil("", { fileName: null, module: true }); + }, Error); +} diff --git a/js/src/jit-test/tests/profiler/native-trampoline-2.js b/js/src/jit-test/tests/profiler/native-trampoline-2.js new file mode 100644 index 0000000000..a85913431b --- /dev/null +++ b/js/src/jit-test/tests/profiler/native-trampoline-2.js @@ -0,0 +1,7 @@ +let arr = [1, 2, 3, 4, 5, 6, 7, 8]; +arr.sort((x, y) => { + enableGeckoProfilingWithSlowAssertions(); + readGeckoProfilingStack(); + return y - x; +}); +assertEq(arr.toString(), "8,7,6,5,4,3,2,1"); diff --git a/js/src/jit-test/tests/profiler/native-trampoline-3.js b/js/src/jit-test/tests/profiler/native-trampoline-3.js new file mode 100644 index 0000000000..16fe547051 --- /dev/null +++ b/js/src/jit-test/tests/profiler/native-trampoline-3.js @@ -0,0 +1,32 @@ +// |jit-test| skip-if: !wasmIsSupported() + +// Use a Wasm module to get the following stack frames: +// +// .. => array sort trampoline => wasmfunc comparator (Wasm) => comparator (JS) + +let binary = wasmTextToBinary(` +(module + (import "" "comparator" (func $comparator (param i32) (param i32) (result i32))) + (func $wasmfunc + (export "wasmfunc") + (param $x i32) + (param $y i32) + (result i32) + (return (call $comparator (local.get $x) (local.get $y))) + ) +)`); +let mod = new WebAssembly.Module(binary); +let instance = new WebAssembly.Instance(mod, {"": {comparator}}); + +function comparator(x, y) { + readGeckoProfilingStack(); + return y - x; +} + +enableGeckoProfilingWithSlowAssertions(); + +for (let i = 0; i < 20; i++) { + let arr = [3, 1, 2, -1, 0, 4]; + arr.sort(instance.exports.wasmfunc); + assertEq(arr.toString(), "4,3,2,1,0,-1"); +} diff --git a/js/src/jit-test/tests/profiler/native-trampoline.js b/js/src/jit-test/tests/profiler/native-trampoline.js new file mode 100644 index 0000000000..e140874a15 --- /dev/null +++ b/js/src/jit-test/tests/profiler/native-trampoline.js @@ -0,0 +1,40 @@ +enableGeckoProfilingWithSlowAssertions(); + +function testBasic() { + var arr = [2, -1]; + var cmp = function(x, y) { + readGeckoProfilingStack(); + return x - y; + }; + for (var i = 0; i < 20; i++) { + arr.sort(cmp); + } +} +testBasic(); + +function testRectifierFrame() { + var arr = [2, -1]; + var cmp = function(x, y, z, a) { + readGeckoProfilingStack(); + return x - y; + }; + for (var i = 0; i < 20; i++) { + arr.sort(cmp); + } +} +testRectifierFrame(); + +function testRectifierFrameCaller() { + var o = {}; + var calls = 0; + Object.defineProperty(o, "length", {get: function() { + calls++; + readGeckoProfilingStack(); + return 0; + }}); + for (var i = 0; i < 20; i++) { + Array.prototype.sort.call(o); + } + assertEq(calls, 20); +} +testRectifierFrameCaller(); diff --git a/js/src/jit-test/tests/profiler/wasm-to-js-1.js b/js/src/jit-test/tests/profiler/wasm-to-js-1.js new file mode 100644 index 0000000000..2ce48f391c --- /dev/null +++ b/js/src/jit-test/tests/profiler/wasm-to-js-1.js @@ -0,0 +1,20 @@ +// |jit-test| skip-if: !wasmIsSupported(); --fast-warmup +function sample() { + enableGeckoProfiling(); + readGeckoProfilingStack(); + disableGeckoProfiling(); +} +const text = `(module + (import "m" "f" (func $f)) + (func (export "test") + (call $f) +))`; +const bytes = wasmTextToBinary(text); +const mod = new WebAssembly.Module(bytes); +const imports = {"m": {"f": sample}}; +const instance = new WebAssembly.Instance(mod, imports); +sample(); +for (let i = 0; i < 5; i++) { + gc(this, "shrinking"); + instance.exports.test(); +} diff --git a/js/src/jit-test/tests/profiler/wasm-to-js-2.js b/js/src/jit-test/tests/profiler/wasm-to-js-2.js new file mode 100644 index 0000000000..3949c3a587 --- /dev/null +++ b/js/src/jit-test/tests/profiler/wasm-to-js-2.js @@ -0,0 +1,19 @@ +// |jit-test| skip-if: !wasmIsSupported() +// Ensure readGeckoProfilingStack finds at least 1 Wasm frame on the stack. +function calledFromWasm() { + let frames = readGeckoProfilingStack().flat(); + assertEq(frames.filter(f => f.kind === "wasm").length >= 1, true); +} +enableGeckoProfiling(); +const text = `(module + (import "m" "f" (func $f)) + (func (export "test") + (call $f) +))`; +const bytes = wasmTextToBinary(text); +const mod = new WebAssembly.Module(bytes); +const imports = {"m": {"f": calledFromWasm}}; +const instance = new WebAssembly.Instance(mod, imports); +for (let i = 0; i < 150; i++) { + instance.exports.test(); +} diff --git a/js/src/jit-test/tests/promise/allSettled-dead.js b/js/src/jit-test/tests/promise/allSettled-dead.js new file mode 100644 index 0000000000..8ae8e53d6b --- /dev/null +++ b/js/src/jit-test/tests/promise/allSettled-dead.js @@ -0,0 +1,20 @@ +newGlobal(); +const g = newGlobal({ + "newCompartment": true, +}); +const p1 = g.eval(` +Promise.resolve(); +`); +const p2 = p1.then(); +nukeAllCCWs(); +ignoreUnhandledRejections(); +Promise.resolve = function() { + return p2; +}; +let caught = false; +Promise.allSettled([1]).catch(e => { + caught = true; + assertEq(e.message.includes("dead object"), true); +}); +drainJobQueue(); +assertEq(caught, true); diff --git a/js/src/jit-test/tests/promise/jobqueue-interrupt-01.js b/js/src/jit-test/tests/promise/jobqueue-interrupt-01.js new file mode 100644 index 0000000000..758680e031 --- /dev/null +++ b/js/src/jit-test/tests/promise/jobqueue-interrupt-01.js @@ -0,0 +1,23 @@ +// catchTermination should undo the quit() operation and let the remaining jobs +// run. + +evaluate(` + quit(); +`, { + catchTermination : true +}); + +const global = newGlobal({ newCompartment: true }); + +let called = false; +const dbg = new Debugger(global); +dbg.onDebuggerStatement = function (frame) { + Promise.resolve(42).then(v => { called = true; }); +}; +global.eval(` + debugger; +`); + +drainJobQueue(); + +assertEq(called, true); diff --git a/js/src/jit-test/tests/promise/jobqueue-interrupt-02.js b/js/src/jit-test/tests/promise/jobqueue-interrupt-02.js new file mode 100644 index 0000000000..8d8f27ef91 --- /dev/null +++ b/js/src/jit-test/tests/promise/jobqueue-interrupt-02.js @@ -0,0 +1,14 @@ +// quit() while draining job queue leaves the remaining jobs untouched. + +const global = newGlobal({ newCompartment:true }); +const dbg = Debugger(global); +dbg.onDebuggerStatement = function() { + Promise.resolve().then(() => { + quit(); + }); + Promise.resolve().then(() => { + // This shouldn't be called. + assertEq(true, false); + }); +}; +global.eval("debugger"); diff --git a/js/src/jit-test/tests/proxy/bug1885774.js b/js/src/jit-test/tests/proxy/bug1885774.js new file mode 100644 index 0000000000..fa88cbf823 --- /dev/null +++ b/js/src/jit-test/tests/proxy/bug1885774.js @@ -0,0 +1,25 @@ +// |jit-test| --no-threads; --fast-warmup + +var {proxy, revoke} = Proxy.revocable({x:1}, {}); + +function foo(o) { + var res = 0; + for (var i = 0; i < 2; i++) { + res += o.x; + } + return res; +} + +with ({}) {} +for (var i = 0; i < 100; i++) { + assertEq(foo(proxy), 2); +} + +revoke(); +var caught = false; +try { + foo(proxy); +} catch { + caught = true; +} +assertEq(caught, true); diff --git a/js/src/jit-test/tests/structured-clone/bug1888727.js b/js/src/jit-test/tests/structured-clone/bug1888727.js new file mode 100644 index 0000000000..7958781c92 --- /dev/null +++ b/js/src/jit-test/tests/structured-clone/bug1888727.js @@ -0,0 +1,21 @@ +function test() { + // Construct a structured clone of a random BigInt value. + const n = 0xfeeddeadbeef2dadfeeddeadbeef2dadfeeddeadbeef2dadfeeddeadbeef2dadn; + const s = serialize(n, [], {scope: 'DifferentProcess'}); + assertEq(deserialize(s), n); + + // Truncate it by chopping off the last 8 bytes. + s.clonebuffer = s.arraybuffer.slice(0, -8); + + // Deserialization should now throw a catchable exception. + try { + deserialize(s); + // The bug was throwing an uncatchable error, so this next assertion won't + // be reached in either the buggy or fixed code. + assertEq(true, false, "should have thrown truncation error"); + } catch (e) { + assertEq(e.message.includes("truncated"), true); + } +} + +test(); diff --git a/js/src/jit-test/tests/structured-clone/tenuring.js b/js/src/jit-test/tests/structured-clone/tenuring.js index 0fffa064fa..cec53a6956 100644 --- a/js/src/jit-test/tests/structured-clone/tenuring.js +++ b/js/src/jit-test/tests/structured-clone/tenuring.js @@ -1,4 +1,4 @@ -// Check that we switch to allocating in the tenure heap after the first +// Check that we switch to allocating in the tenured heap after the first // nursery collection. function buildObjectTree(depth) { @@ -82,6 +82,7 @@ function countHeapLocations(tree, objectTree, counts) { gczeal(0); gcparam('minNurseryBytes', 1024 * 1024); gcparam('maxNurseryBytes', 1024 * 1024); +gcparam('semispaceNurseryEnabled', 0); gc(); testRoundTrip(1, true, true); diff --git a/js/src/jit-test/tests/typedarray/resizable-typedarray-from-pinned-buffer.js b/js/src/jit-test/tests/typedarray/resizable-typedarray-from-pinned-buffer.js new file mode 100644 index 0000000000..b17c7c0157 --- /dev/null +++ b/js/src/jit-test/tests/typedarray/resizable-typedarray-from-pinned-buffer.js @@ -0,0 +1,9 @@ +// |jit-test| --enable-arraybuffer-resizable + +let ab = new ArrayBuffer(8, {maxByteLength: 10}); + +pinArrayBufferOrViewLength(ab); + +let ta = new Int8Array(ab); + +assertEq(ta.length, 8); diff --git a/js/src/jit-test/tests/warp/bug1876425.js b/js/src/jit-test/tests/warp/bug1876425.js new file mode 100644 index 0000000000..aca528aac6 --- /dev/null +++ b/js/src/jit-test/tests/warp/bug1876425.js @@ -0,0 +1,62 @@ +// 1) Trial inline f1 => g (g1) => h. +// 2) Set g to g2, to fail the f1 => g1 call site. +// 3) Set g to g1 again. +// 4) Make g1's generic ICScript trial inline a different callee, h2. +// 5) Bail out from f1 => g1 => h. +// +// The bailout must not confuse the ICScripts of h1 and h2. + +function noninlined1(x) { + with (this) {}; + if (x === 4002) { + // Step 4. + f2(); + // Step 5. + return true; + } + return false; +} +function noninlined2(x) { + with (this) {}; + if (x === 4000) { + // Step 2. + g = (h, x) => { + return x + 1; + }; + } + if (x === 4001) { + // Step 3. + g = g1; + } +} +var h = function(x) { + if (noninlined1(x)) { + // Step 5. + bailout(); + } + return x + 1; +}; +var g = function(callee, x) { + return callee(x) + 1; +}; +var g1 = g; + +function f2() { + var h2 = x => x + 1; + for (var i = 0; i < 300; i++) { + var x = (i % 2 === 0) ? "foo" : i; // Force trial inlining. + g1(h2, x); + } +} + +function f1() { + for (var i = 0; i < 4200; i++) { + var x = (i < 900 && i % 2 === 0) ? "foo" : i; // Force trial inlining. + g(h, x); + noninlined2(i); + if (i === 200) { + trialInline(); + } + } +} +f1(); diff --git a/js/src/jit-test/tests/wasm/directiveless/bug1877358.js b/js/src/jit-test/tests/wasm/directiveless/bug1877358.js index 10cb54398a..8d512efcfe 100644 --- a/js/src/jit-test/tests/wasm/directiveless/bug1877358.js +++ b/js/src/jit-test/tests/wasm/directiveless/bug1877358.js @@ -1,4 +1,4 @@ -// |jit-test| -P wasm_exceptions=false; include:wasm.js +// |jit-test| include:wasm.js let {test} = wasmEvalText(`(module (func $m (import "" "m")) diff --git a/js/src/jit-test/tests/wasm/gc/casting.js b/js/src/jit-test/tests/wasm/gc/casting.js index a71a589db8..3b550e6415 100644 --- a/js/src/jit-test/tests/wasm/gc/casting.js +++ b/js/src/jit-test/tests/wasm/gc/casting.js @@ -114,3 +114,72 @@ function testAllCasts(types) { } } testAllCasts(TYPES); + +// Test that combinations of ref.test and ref.cast compile correctly. +// (These can be optimized together.) +{ + const { make, test1, test2, test3, test4 } = wasmEvalText(`(module + (type $a (array i32)) + (func (export "make") (param i32) (result anyref) + local.get 0 + local.get 0 + array.new_fixed $a 2 + ) + (func (export "test1") (param anyref) (result i32) + (if (ref.test (ref $a) (local.get 0)) + (then + (ref.cast (ref $a) (local.get 0)) + (array.get $a (i32.const 0)) + return + ) + ) + i32.const -1 + ) + (func (export "test2") (param anyref) (result i32) + (if (ref.test (ref $a) (local.get 0)) + (then) + (else + (ref.cast (ref $a) (local.get 0)) + (array.get $a (i32.const 0)) + return + ) + ) + i32.const -1 + ) + (func (export "test3") (param anyref) (result i32) + (if (ref.test (ref $a) (local.get 0)) + (then + (if (ref.test (ref $a) (local.get 0)) + (then) + (else + (ref.cast (ref $a) (local.get 0)) + (array.get $a (i32.const 0)) + return + ) + ) + ) + ) + i32.const -1 + ) + (func (export "test4") (param anyref) (result i32) + (if (ref.test (ref $a) (local.get 0)) + (then + (if (ref.test (ref $a) (local.get 0)) + (then + local.get 0 + ref.cast (ref $a) + ref.cast (ref $a) + (array.get $a (i32.const 0)) + return + ) + ) + ) + ) + i32.const -1 + ) + )`).exports; + assertEq(test1(make(99)), 99); + assertEq(test2(make(99)), -1); + assertEq(test3(make(99)), -1); + assertEq(test4(make(99)), 99); +} diff --git a/js/src/jit-test/tests/wasm/gc/i31ref.js b/js/src/jit-test/tests/wasm/gc/i31ref.js index 65f2fccc3f..298447e848 100644 --- a/js/src/jit-test/tests/wasm/gc/i31ref.js +++ b/js/src/jit-test/tests/wasm/gc/i31ref.js @@ -149,6 +149,24 @@ for (const {input, expected} of bigI32Tests) { assertEq(getElem(), expected); } +// Test that (ref.i31 (i32 const value)) optimization is correct +for (let value of WasmI31refValues) { + let {compare} = wasmEvalText(`(module + (func $innerCompare (param i32) (param i31ref) (result i32) + (ref.eq + (ref.i31 local.get 0) + local.get 1 + ) + ) + (func (export "compare") (result i32) + i32.const ${value} + (ref.i31 i32.const ${value}) + call $innerCompare + ) +)`).exports; + assertEq(compare(value), 1); +} + const { i31GetU_null, i31GetS_null } = wasmEvalText(`(module (func (export "i31GetU_null") (result i32) ref.null i31 diff --git a/js/src/jit-test/tests/wasm/regress/bug1886870.js b/js/src/jit-test/tests/wasm/regress/bug1886870.js new file mode 100644 index 0000000000..a4947bd91a --- /dev/null +++ b/js/src/jit-test/tests/wasm/regress/bug1886870.js @@ -0,0 +1,8 @@ +// Check proper handling of OOM after toQuotedString(). + +oomTest(function () { + new WebAssembly.Instance( + new WebAssembly.Module(wasmTextToBinary('(import "m" "f" (func $f))')), + {} + ); +}); diff --git a/js/src/jit-test/tests/wasm/regress/bug1887535.js b/js/src/jit-test/tests/wasm/regress/bug1887535.js new file mode 100644 index 0000000000..e2793831bf --- /dev/null +++ b/js/src/jit-test/tests/wasm/regress/bug1887535.js @@ -0,0 +1,25 @@ +// |jit-test| slow; + +// Tests the exception handling works during stack overflow. +const v1 = newGlobal({sameZoneAs: this}); +class C2 { + static { } +} + +function f() { v1.constructor; } + +const { test } = wasmEvalText(` +(module + (import "" "f" (func $f)) + (export "test" (func $f)) +)`, { "": { f, },}).exports; + + +function f4() { + try { + f4(); + } catch(_) { + test(); test(); + } +} +f4(); diff --git a/js/src/jit-test/tests/wasm/regress/bug1887596.js b/js/src/jit-test/tests/wasm/regress/bug1887596.js new file mode 100644 index 0000000000..8ff579fc35 --- /dev/null +++ b/js/src/jit-test/tests/wasm/regress/bug1887596.js @@ -0,0 +1,14 @@ +const t = ` + (module + (func $f (result f32) + f32.const 1.25 + ) + (table (export "table") 10 funcref) + (elem (i32.const 0) $f) + )`; +const i = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(t))); +const f = i.exports.table.get(0); + +// These FP equality comparisons are safe because 1.25 is representable exactly. +assertEq(1.25, f()); +assertEq(1.25, this.wasmLosslessInvoke(f).value); |