Bug 1358501 - Ion optimize JSOP_OPTIMIZE_SPREADCALL with a constant when Array iterator properties are in their initial state. r=jandem
authorAndré Bargull <andre.bargull@gmail.com>
Mon, 18 Feb 2019 05:58:09 -0800
changeset 519563 e3d1480593cfa2a086e020cea30d2af9ac20fbe7
parent 519562 75af9e205c2887e3dffdca703d970eec92e83d08
child 519564 09f23a363606830752a90a88dc452dd6d58ec10e
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1358501
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1358501 - Ion optimize JSOP_OPTIMIZE_SPREADCALL with a constant when Array iterator properties are in their initial state. r=jandem
js/src/jit-test/tests/ion/spreadcall-not-optimized-dynamic-1.js
js/src/jit-test/tests/ion/spreadcall-not-optimized-dynamic-2.js
js/src/jit-test/tests/ion/spreadcall-not-optimized-dynamic-3.js
js/src/jit-test/tests/ion/spreadcall-not-optimized-dynamic-4a.js
js/src/jit-test/tests/ion/spreadcall-not-optimized-dynamic-4b.js
js/src/jit-test/tests/ion/spreadcall-not-optimized-dynamic-5a.js
js/src/jit-test/tests/ion/spreadcall-not-optimized-dynamic-5b.js
js/src/jit-test/tests/ion/spreadcall-not-optimized-dynamic-6a.js
js/src/jit-test/tests/ion/spreadcall-not-optimized-dynamic-6b.js
js/src/jit-test/tests/ion/spreadcall-not-optimized-static-1.js
js/src/jit-test/tests/ion/spreadcall-not-optimized-static-2.js
js/src/jit-test/tests/ion/spreadcall-not-optimized-static-3.js
js/src/jit-test/tests/ion/spreadcall-not-optimized-static-4a.js
js/src/jit-test/tests/ion/spreadcall-not-optimized-static-4b.js
js/src/jit-test/tests/ion/spreadcall-not-optimized-static-5a.js
js/src/jit-test/tests/ion/spreadcall-not-optimized-static-5b.js
js/src/jit-test/tests/ion/spreadcall-not-optimized-static-6a.js
js/src/jit-test/tests/ion/spreadcall-not-optimized-static-6b.js
js/src/jit/IonBuilder.cpp
js/src/jit/IonBuilder.h
js/src/vm/GlobalObject.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/spreadcall-not-optimized-dynamic-1.js
@@ -0,0 +1,44 @@
+// Tests when JSOP_OPTIMIZE_SPREADCALL no longer apply after the initial Ion
+// compilation.
+
+// JSOP_OPTIMIZE_SPREADCALL can be optimised when the following conditions
+// are fulfilled:
+//   (1) the argument is an array
+//   (2) the array has no hole
+//   (3) array[@@iterator] is not modified
+//   (4) the array's prototype is Array.prototype
+//   (5) Array.prototype[@@iterator] is not modified
+//   (6) %ArrayIteratorPrototype%.next is not modified
+
+function add(a, b) {
+    return a + b;
+}
+
+// The rest argument is overwritten with a non-Array object.
+function test() {
+    var badRest = {
+        *[Symbol.iterator]() {
+            yield 3;
+            yield 4;
+        }
+    };
+    function maybeInvalidate(rest) {
+        // Use a WithStatement to prevent Ion-inlining. This ensures any
+        // bailouts due to type changes don't occur in this function, but
+        // instead in the caller.
+        with ({});
+
+        if (i >= 1900) {
+            return badRest;
+        }
+        return rest;
+    }
+    function fn(...rest) {
+        rest = maybeInvalidate(rest);
+        return add(...rest);
+    }
+    for (var i = 0; i < 4000; ++i) {
+        assertEq(fn(1, 2), i < 1900 ? 3 : 7);
+    }
+}
+test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/spreadcall-not-optimized-dynamic-2.js
@@ -0,0 +1,37 @@
+// Tests when JSOP_OPTIMIZE_SPREADCALL no longer apply after the initial Ion
+// compilation.
+
+// JSOP_OPTIMIZE_SPREADCALL can be optimised when the following conditions
+// are fulfilled:
+//   (1) the argument is an array
+//   (2) the array has no hole
+//   (3) array[@@iterator] is not modified
+//   (4) the array's prototype is Array.prototype
+//   (5) Array.prototype[@@iterator] is not modified
+//   (6) %ArrayIteratorPrototype%.next is not modified
+
+function add(a, b, c = 0, d = 0) {
+    return a + b + c + d;
+}
+
+// The rest argument contains holes.
+function test() {
+    function maybeInvalidate(rest) {
+        // Use a WithStatement to prevent Ion-inlining. This ensures any
+        // bailouts due to type changes don't occur in this function, but
+        // instead in the caller.
+        with ({});
+
+        if (i >= 1900) {
+            rest[3] = 4;
+        }
+    }
+    function fn(...rest) {
+        maybeInvalidate(rest);
+        return add(...rest);
+    }
+    for (var i = 0; i < 4000; ++i) {
+        assertEq(fn(1, 2), i < 1900 ? 3 : 7);
+    }
+}
+test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/spreadcall-not-optimized-dynamic-3.js
@@ -0,0 +1,40 @@
+// Tests when JSOP_OPTIMIZE_SPREADCALL no longer apply after the initial Ion
+// compilation.
+
+// JSOP_OPTIMIZE_SPREADCALL can be optimised when the following conditions
+// are fulfilled:
+//   (1) the argument is an array
+//   (2) the array has no hole
+//   (3) array[@@iterator] is not modified
+//   (4) the array's prototype is Array.prototype
+//   (5) Array.prototype[@@iterator] is not modified
+//   (6) %ArrayIteratorPrototype%.next is not modified
+
+function add(a, b) {
+    return a + b;
+}
+
+// The rest argument has an own @@iterator property.
+function test() {
+    function MyIter() {
+        return [3, 4][Symbol.iterator]();
+    }
+    function maybeInvalidate(rest) {
+        // Use a WithStatement to prevent Ion-inlining. This ensures any
+        // bailouts due to type changes don't occur in this function, but
+        // instead in the caller.
+        with ({});
+
+        if (i >= 1900) {
+            rest[Symbol.iterator] = MyIter;
+        }
+    }
+    function fn(...rest) {
+        maybeInvalidate(rest);
+        return add(...rest);
+    }
+    for (var i = 0; i < 4000; ++i) {
+        assertEq(fn(1, 2), i < 1900 ? 3 : 7);
+    }
+}
+test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/spreadcall-not-optimized-dynamic-4a.js
@@ -0,0 +1,43 @@
+// Tests when JSOP_OPTIMIZE_SPREADCALL no longer apply after the initial Ion
+// compilation.
+
+// JSOP_OPTIMIZE_SPREADCALL can be optimised when the following conditions
+// are fulfilled:
+//   (1) the argument is an array
+//   (2) the array has no hole
+//   (3) array[@@iterator] is not modified
+//   (4) the array's prototype is Array.prototype
+//   (5) Array.prototype[@@iterator] is not modified
+//   (6) %ArrayIteratorPrototype%.next is not modified
+
+function add(a, b) {
+    return a + b;
+}
+
+// The rest arguments don't share a common prototype.
+function test() {
+    class MyArray1 extends Array { }
+    class MyArray2 extends Array { }
+    function maybeInvalidate(rest) {
+        // Use a WithStatement to prevent Ion-inlining. This ensures any
+        // bailouts due to type changes don't occur in this function, but
+        // instead in the caller.
+        with ({});
+
+        if (i >= 1900) {
+            if (i & 1)
+                rest = new MyArray1(3, 4);
+            else
+                rest = new MyArray2(5, 6);
+        }
+        return rest;
+    }
+    function fn(...rest) {
+        rest = maybeInvalidate(rest);
+        return add(...rest);
+    }
+    for (var i = 0; i < 4000; ++i) {
+        assertEq(fn(1, 2), i < 1900 ? 3 : (i & 1) ? 7 : 11);
+    }
+}
+test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/spreadcall-not-optimized-dynamic-4b.js
@@ -0,0 +1,39 @@
+// Tests when JSOP_OPTIMIZE_SPREADCALL no longer apply after the initial Ion
+// compilation.
+
+// JSOP_OPTIMIZE_SPREADCALL can be optimised when the following conditions
+// are fulfilled:
+//   (1) the argument is an array
+//   (2) the array has no hole
+//   (3) array[@@iterator] is not modified
+//   (4) the array's prototype is Array.prototype
+//   (5) Array.prototype[@@iterator] is not modified
+//   (6) %ArrayIteratorPrototype%.next is not modified
+
+function add(a, b) {
+    return a + b;
+}
+
+// The rest argument's prototype isn't Array.prototype.
+function test() {
+    class MyArray extends Array { }
+    function maybeInvalidate(rest) {
+        // Use a WithStatement to prevent Ion-inlining. This ensures any
+        // bailouts due to type changes don't occur in this function, but
+        // instead in the caller.
+        with ({});
+
+        if (i >= 1900) {
+            rest = new MyArray(3, 4);
+        }
+        return rest;
+    }
+    function fn(...rest) {
+        rest = maybeInvalidate(rest);
+        return add(...rest);
+    }
+    for (var i = 0; i < 4000; ++i) {
+        assertEq(fn(1, 2), i < 1900 ? 3 : 7);
+    }
+}
+test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/spreadcall-not-optimized-dynamic-5a.js
@@ -0,0 +1,37 @@
+// Tests when JSOP_OPTIMIZE_SPREADCALL no longer apply after the initial Ion
+// compilation.
+
+// JSOP_OPTIMIZE_SPREADCALL can be optimised when the following conditions
+// are fulfilled:
+//   (1) the argument is an array
+//   (2) the array has no hole
+//   (3) array[@@iterator] is not modified
+//   (4) the array's prototype is Array.prototype
+//   (5) Array.prototype[@@iterator] is not modified
+//   (6) %ArrayIteratorPrototype%.next is not modified
+
+function add(a, b) {
+    return a + b;
+}
+
+// Array.prototype's [[Prototype]] was changed, as a result all properties are marked as unknown.
+function test() {
+    function maybeInvalidate() {
+        // Use a WithStatement to prevent Ion-inlining. This ensures any
+        // bailouts due to type changes don't occur in this function, but
+        // instead in the caller.
+        with ({});
+
+        if (i === 1900) {
+            Object.setPrototypeOf(Array.prototype, null);
+        }
+    }
+    function fn(...rest) {
+        maybeInvalidate();
+        return add(...rest);
+    }
+    for (var i = 0; i < 4000; ++i) {
+        assertEq(fn(1, 2), 3);
+    }
+}
+test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/spreadcall-not-optimized-dynamic-5b.js
@@ -0,0 +1,40 @@
+// Tests when JSOP_OPTIMIZE_SPREADCALL no longer apply after the initial Ion
+// compilation.
+
+// JSOP_OPTIMIZE_SPREADCALL can be optimised when the following conditions
+// are fulfilled:
+//   (1) the argument is an array
+//   (2) the array has no hole
+//   (3) array[@@iterator] is not modified
+//   (4) the array's prototype is Array.prototype
+//   (5) Array.prototype[@@iterator] is not modified
+//   (6) %ArrayIteratorPrototype%.next is not modified
+
+function add(a, b) {
+    return a + b;
+}
+
+// Array.prototype[@@iterator] was modified.
+function test() {
+    function maybeInvalidate() {
+        // Use a WithStatement to prevent Ion-inlining. This ensures any
+        // bailouts due to type changes don't occur in this function, but
+        // instead in the caller.
+        with ({});
+
+        if (i === 1900) {
+            var ArrayPrototypeIterator = Array.prototype[Symbol.iterator];
+            Array.prototype[Symbol.iterator] = function() {
+                return ArrayPrototypeIterator.call([3, 4]);
+            };
+        }
+    }
+    function fn(...rest) {
+        maybeInvalidate();
+        return add(...rest);
+    }
+    for (var i = 0; i < 4000; ++i) {
+        assertEq(fn(1, 2), i < 1900 ? 3 : 7);
+    }
+}
+test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/spreadcall-not-optimized-dynamic-6a.js
@@ -0,0 +1,38 @@
+// Tests when JSOP_OPTIMIZE_SPREADCALL no longer apply after the initial Ion
+// compilation.
+
+// JSOP_OPTIMIZE_SPREADCALL can be optimised when the following conditions
+// are fulfilled:
+//   (1) the argument is an array
+//   (2) the array has no hole
+//   (3) array[@@iterator] is not modified
+//   (4) the array's prototype is Array.prototype
+//   (5) Array.prototype[@@iterator] is not modified
+//   (6) %ArrayIteratorPrototype%.next is not modified
+
+function add(a, b) {
+    return a + b;
+}
+
+// %ArrayIteratorPrototype%'s [[Prototype]] was changed, as a result all properties are marked as unknown.
+function test() {
+    function maybeInvalidate() {
+        // Use a WithStatement to prevent Ion-inlining. This ensures any
+        // bailouts due to type changes don't occur in this function, but
+        // instead in the caller.
+        with ({});
+
+        if (i === 1900) {
+            var ArrayIteratorPrototype = Object.getPrototypeOf(Array.prototype[Symbol.iterator]());
+            Object.setPrototypeOf(ArrayIteratorPrototype, null);
+        }
+    }
+    function fn(...rest) {
+        maybeInvalidate();
+        return add(...rest);
+    }
+    for (var i = 0; i < 4000; ++i) {
+        assertEq(fn(1, 2), 3);
+    }
+}
+test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/spreadcall-not-optimized-dynamic-6b.js
@@ -0,0 +1,45 @@
+// Tests when JSOP_OPTIMIZE_SPREADCALL no longer apply after the initial Ion
+// compilation.
+
+// JSOP_OPTIMIZE_SPREADCALL can be optimised when the following conditions
+// are fulfilled:
+//   (1) the argument is an array
+//   (2) the array has no hole
+//   (3) array[@@iterator] is not modified
+//   (4) the array's prototype is Array.prototype
+//   (5) Array.prototype[@@iterator] is not modified
+//   (6) %ArrayIteratorPrototype%.next is not modified
+
+function add(a, b) {
+    return a + b;
+}
+
+// %ArrayIteratorPrototype%.next was modified.
+function test() {
+    function maybeInvalidate() {
+        // Use a WithStatement to prevent Ion-inlining. This ensures any
+        // bailouts due to type changes don't occur in this function, but
+        // instead in the caller.
+        with ({});
+
+        if (i === 1900) {
+            var ArrayIteratorPrototype = Object.getPrototypeOf(Array.prototype[Symbol.iterator]());
+            var ArrayIteratorPrototypeNext = ArrayIteratorPrototype.next;
+            ArrayIteratorPrototype.next = function() {
+                var res = ArrayIteratorPrototypeNext.call(this);
+                if (!res.done) {
+                    res.value += 2;
+                }
+                return res;
+            };
+        }
+    }
+    function fn(...rest) {
+        maybeInvalidate();
+        return add(...rest);
+    }
+    for (var i = 0; i < 4000; ++i) {
+        assertEq(fn(1, 2), i < 1900 ? 3 : 7);
+    }
+}
+test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/spreadcall-not-optimized-static-1.js
@@ -0,0 +1,33 @@
+// Tests when JSOP_OPTIMIZE_SPREADCALL can't be applied during the initial
+// Ion compilation.
+
+// JSOP_OPTIMIZE_SPREADCALL can be optimised when the following conditions
+// are fulfilled:
+//   (1) the argument is an array
+//   (2) the array has no hole
+//   (3) array[@@iterator] is not modified
+//   (4) the array's prototype is Array.prototype
+//   (5) Array.prototype[@@iterator] is not modified
+//   (6) %ArrayIteratorPrototype%.next is not modified
+
+function add(a, b) {
+    return a + b;
+}
+
+// The rest argument is overwritten with a non-Array object.
+function test() {
+    var badRest = {
+        *[Symbol.iterator]() {
+            yield 3;
+            yield 4;
+        }
+    };
+    function fn(...rest) {
+        rest = badRest;
+        return add(...rest);
+    }
+    for (var i = 0; i < 2000; ++i) {
+        assertEq(fn(1, 2), 7);
+    }
+}
+test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/spreadcall-not-optimized-static-2.js
@@ -0,0 +1,27 @@
+// Tests when JSOP_OPTIMIZE_SPREADCALL can't be applied during the initial
+// Ion compilation.
+
+// JSOP_OPTIMIZE_SPREADCALL can be optimised when the following conditions
+// are fulfilled:
+//   (1) the argument is an array
+//   (2) the array has no hole
+//   (3) array[@@iterator] is not modified
+//   (4) the array's prototype is Array.prototype
+//   (5) Array.prototype[@@iterator] is not modified
+//   (6) %ArrayIteratorPrototype%.next is not modified
+
+function add(a, b, c = 0, d = 0) {
+    return a + b + c + d;
+}
+
+// The rest argument contains holes.
+function test() {
+    function fn(...rest) {
+        rest[3] = 4;
+        return add(...rest);
+    }
+    for (var i = 0; i < 2000; ++i) {
+        assertEq(fn(1, 2), 7);
+    }
+}
+test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/spreadcall-not-optimized-static-3.js
@@ -0,0 +1,30 @@
+// Tests when JSOP_OPTIMIZE_SPREADCALL can't be applied during the initial
+// Ion compilation.
+
+// JSOP_OPTIMIZE_SPREADCALL can be optimised when the following conditions
+// are fulfilled:
+//   (1) the argument is an array
+//   (2) the array has no hole
+//   (3) array[@@iterator] is not modified
+//   (4) the array's prototype is Array.prototype
+//   (5) Array.prototype[@@iterator] is not modified
+//   (6) %ArrayIteratorPrototype%.next is not modified
+
+function add(a, b) {
+    return a + b;
+}
+
+// The rest argument has an own @@iterator property.
+function test() {
+    function MyIter() {
+        return [3, 4][Symbol.iterator]();
+    }
+    function fn(...rest) {
+        rest[Symbol.iterator] = MyIter;
+        return add(...rest);
+    }
+    for (var i = 0; i < 2000; ++i) {
+        assertEq(fn(1, 2), 7);
+    }
+}
+test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/spreadcall-not-optimized-static-4a.js
@@ -0,0 +1,32 @@
+// Tests when JSOP_OPTIMIZE_SPREADCALL can't be applied during the initial
+// Ion compilation.
+
+// JSOP_OPTIMIZE_SPREADCALL can be optimised when the following conditions
+// are fulfilled:
+//   (1) the argument is an array
+//   (2) the array has no hole
+//   (3) array[@@iterator] is not modified
+//   (4) the array's prototype is Array.prototype
+//   (5) Array.prototype[@@iterator] is not modified
+//   (6) %ArrayIteratorPrototype%.next is not modified
+
+function add(a, b) {
+    return a + b;
+}
+
+// The rest arguments don't share a common prototype.
+function test() {
+    class MyArray1 extends Array { }
+    class MyArray2 extends Array { }
+    function fn(...rest) {
+        if (i & 1)
+            rest = new MyArray1(3, 4);
+        else
+            rest = new MyArray2(5, 6);
+        return add(...rest);
+    }
+    for (var i = 0; i < 2000; ++i) {
+        assertEq(fn(1, 2), (i & 1) ? 7 : 11);
+    }
+}
+test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/spreadcall-not-optimized-static-4b.js
@@ -0,0 +1,28 @@
+// Tests when JSOP_OPTIMIZE_SPREADCALL can't be applied during the initial
+// Ion compilation.
+
+// JSOP_OPTIMIZE_SPREADCALL can be optimised when the following conditions
+// are fulfilled:
+//   (1) the argument is an array
+//   (2) the array has no hole
+//   (3) array[@@iterator] is not modified
+//   (4) the array's prototype is Array.prototype
+//   (5) Array.prototype[@@iterator] is not modified
+//   (6) %ArrayIteratorPrototype%.next is not modified
+
+function add(a, b) {
+    return a + b;
+}
+
+// The rest argument's prototype isn't Array.prototype.
+function test() {
+    class MyArray extends Array { }
+    function fn(...rest) {
+        rest = new MyArray(3, 4);
+        return add(...rest);
+    }
+    for (var i = 0; i < 2000; ++i) {
+        assertEq(fn(1, 2), 7);
+    }
+}
+test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/spreadcall-not-optimized-static-5a.js
@@ -0,0 +1,27 @@
+// Tests when JSOP_OPTIMIZE_SPREADCALL can't be applied during the initial
+// Ion compilation.
+
+// JSOP_OPTIMIZE_SPREADCALL can be optimised when the following conditions
+// are fulfilled:
+//   (1) the argument is an array
+//   (2) the array has no hole
+//   (3) array[@@iterator] is not modified
+//   (4) the array's prototype is Array.prototype
+//   (5) Array.prototype[@@iterator] is not modified
+//   (6) %ArrayIteratorPrototype%.next is not modified
+
+function add(a, b) {
+    return a + b;
+}
+
+// Array.prototype's [[Prototype]] was changed, as a result all properties are marked as unknown.
+function test() {
+    Object.setPrototypeOf(Array.prototype, null);
+    function fn(...rest) {
+        return add(...rest);
+    }
+    for (var i = 0; i < 2000; ++i) {
+        assertEq(fn(1, 2), 3);
+    }
+}
+test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/spreadcall-not-optimized-static-5b.js
@@ -0,0 +1,30 @@
+// Tests when JSOP_OPTIMIZE_SPREADCALL can't be applied during the initial
+// Ion compilation.
+
+// JSOP_OPTIMIZE_SPREADCALL can be optimised when the following conditions
+// are fulfilled:
+//   (1) the argument is an array
+//   (2) the array has no hole
+//   (3) array[@@iterator] is not modified
+//   (4) the array's prototype is Array.prototype
+//   (5) Array.prototype[@@iterator] is not modified
+//   (6) %ArrayIteratorPrototype%.next is not modified
+
+function add(a, b) {
+    return a + b;
+}
+
+// Array.prototype[@@iterator] was modified.
+function test() {
+    var ArrayPrototypeIterator = Array.prototype[Symbol.iterator];
+    Array.prototype[Symbol.iterator] = function() {
+        return ArrayPrototypeIterator.call([3, 4]);
+    };
+    function fn(...rest) {
+        return add(...rest);
+    }
+    for (var i = 0; i < 2000; ++i) {
+        assertEq(fn(1, 2), 7);
+    }
+}
+test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/spreadcall-not-optimized-static-6a.js
@@ -0,0 +1,28 @@
+// Tests when JSOP_OPTIMIZE_SPREADCALL can't be applied during the initial
+// Ion compilation.
+
+// JSOP_OPTIMIZE_SPREADCALL can be optimised when the following conditions
+// are fulfilled:
+//   (1) the argument is an array
+//   (2) the array has no hole
+//   (3) array[@@iterator] is not modified
+//   (4) the array's prototype is Array.prototype
+//   (5) Array.prototype[@@iterator] is not modified
+//   (6) %ArrayIteratorPrototype%.next is not modified
+
+function add(a, b) {
+    return a + b;
+}
+
+// %ArrayIteratorPrototype%'s [[Prototype]] was changed, as a result all properties are marked as unknown.
+function test() {
+    var ArrayIteratorPrototype = Object.getPrototypeOf(Array.prototype[Symbol.iterator]());
+    Object.setPrototypeOf(ArrayIteratorPrototype, null);
+    function fn(...rest) {
+        return add(...rest);
+    }
+    for (var i = 0; i < 2000; ++i) {
+        assertEq(fn(1, 2), 3);
+    }
+}
+test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/spreadcall-not-optimized-static-6b.js
@@ -0,0 +1,35 @@
+// Tests when JSOP_OPTIMIZE_SPREADCALL can't be applied during the initial
+// Ion compilation.
+
+// JSOP_OPTIMIZE_SPREADCALL can be optimised when the following conditions
+// are fulfilled:
+//   (1) the argument is an array
+//   (2) the array has no hole
+//   (3) array[@@iterator] is not modified
+//   (4) the array's prototype is Array.prototype
+//   (5) Array.prototype[@@iterator] is not modified
+//   (6) %ArrayIteratorPrototype%.next is not modified
+
+function add(a, b) {
+    return a + b;
+}
+
+// %ArrayIteratorPrototype%.next was modified.
+function test() {
+    var ArrayIteratorPrototype = Object.getPrototypeOf(Array.prototype[Symbol.iterator]());
+    var ArrayIteratorPrototypeNext = ArrayIteratorPrototype.next;
+    ArrayIteratorPrototype.next = function() {
+        var res = ArrayIteratorPrototypeNext.call(this);
+        if (!res.done) {
+            res.value += 2;
+        }
+        return res;
+    };
+    function fn(...rest) {
+        return add(...rest);
+    }
+    for (var i = 0; i < 2000; ++i) {
+        assertEq(fn(1, 2), 7);
+    }
+}
+test();
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -20,16 +20,17 @@
 #include "jit/IonOptimizationLevels.h"
 #include "jit/JitSpewer.h"
 #include "jit/Lowering.h"
 #include "jit/MIRGraph.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/EnvironmentObject.h"
 #include "vm/Opcodes.h"
 #include "vm/RegExpStatics.h"
+#include "vm/SelfHosting.h"
 #include "vm/TraceLogging.h"
 
 #include "gc/Nursery-inl.h"
 #include "jit/CompileInfo-inl.h"
 #include "jit/shared/Lowering-shared-inl.h"
 #include "vm/BytecodeUtil-inl.h"
 #include "vm/EnvironmentObject-inl.h"
 #include "vm/JSScript-inl.h"
@@ -2429,24 +2430,18 @@ AbortReasonOr<Ok> IonBuilder::inspectOpc
 #endif
       return Ok();
     }
 
     case JSOP_IS_CONSTRUCTING:
       pushConstant(MagicValue(JS_IS_CONSTRUCTING));
       return Ok();
 
-    case JSOP_OPTIMIZE_SPREADCALL: {
-      // Assuming optimization isn't available doesn't affect correctness.
-      // TODO: Investigate dynamic checks.
-      MDefinition* arr = current->peek(-1);
-      arr->setImplicitlyUsedUnchecked();
-      pushConstant(BooleanValue(false));
-      return Ok();
-    }
+    case JSOP_OPTIMIZE_SPREADCALL:
+      return jsop_optimize_spreadcall();
 
     case JSOP_IMPORTMETA:
       return jsop_importmeta();
 
     case JSOP_DYNAMIC_IMPORT:
       return jsop_dynamic_import();
 
     case JSOP_LOOPENTRY:
@@ -5612,16 +5607,125 @@ AbortReasonOr<Ok> IonBuilder::jsop_sprea
     apply->setNotCrossRealm();
   }
 
   // TypeBarrier the call result
   TemporaryTypeSet* types = bytecodeTypes(pc);
   return pushTypeBarrier(apply, types, BarrierKind::TypeSet);
 }
 
+bool IonBuilder::propertyIsConstantFunction(NativeObject* nobj, jsid id,
+                                            bool (*test)(IonBuilder* builder,
+                                                         JSFunction* fun)) {
+  if (!nobj->isSingleton()) {
+    return false;
+  }
+
+  TypeSet::ObjectKey* objKey = TypeSet::ObjectKey::get(nobj);
+  if (analysisContext) {
+    objKey->ensureTrackedProperty(analysisContext, id);
+  }
+
+  if (objKey->unknownProperties()) {
+    return false;
+  }
+
+  HeapTypeSetKey property = objKey->property(id);
+  Value value = UndefinedValue();
+  if (!property.constant(constraints(), &value)) {
+    return false;
+  }
+  return value.isObject() && value.toObject().is<JSFunction>() &&
+         test(this, &value.toObject().as<JSFunction>());
+}
+
+bool IonBuilder::ensureArrayPrototypeIteratorNotModified() {
+  NativeObject* obj = script()->global().maybeGetArrayPrototype();
+  if (!obj) {
+    return false;
+  }
+
+  jsid id = SYMBOL_TO_JSID(realm->runtime()->wellKnownSymbols().iterator);
+  return propertyIsConstantFunction(obj, id, [](auto* builder, auto* fun) {
+    return IsSelfHostedFunctionWithName(fun,
+                                        builder->runtime->names().ArrayValues);
+  });
+}
+
+bool IonBuilder::ensureArrayIteratorPrototypeNextNotModified() {
+  NativeObject* obj = script()->global().maybeGetArrayIteratorPrototype();
+  if (!obj) {
+    return false;
+  }
+
+  jsid id = NameToId(runtime->names().next);
+  return propertyIsConstantFunction(obj, id, [](auto* builder, auto* fun) {
+    return IsSelfHostedFunctionWithName(
+        fun, builder->runtime->names().ArrayIteratorNext);
+  });
+}
+
+AbortReasonOr<Ok> IonBuilder::jsop_optimize_spreadcall() {
+  MDefinition* arr = current->peek(-1);
+  arr->setImplicitlyUsedUnchecked();
+
+  // Assuming optimization isn't available doesn't affect correctness.
+  // TODO: Investigate dynamic checks.
+  bool result = false;
+  do {
+    // Inline with a constant if the conditions described in
+    // js::OptimizeSpreadCall() are all met or can be expressed through
+    // compiler constraints.
+
+    // The argument is an array.
+    TemporaryTypeSet* types = arr->resultTypeSet();
+    if (!types || types->getKnownClass(constraints()) != &ArrayObject::class_) {
+      break;
+    }
+
+    // The array has no hole.
+    if (types->hasObjectFlags(constraints(), OBJECT_FLAG_NON_PACKED)) {
+      break;
+    }
+
+    // The array's prototype is Array.prototype.
+    JSObject* proto;
+    if (!types->getCommonPrototype(constraints(), &proto)) {
+      break;
+    }
+    NativeObject* arrayProto = script()->global().maybeGetArrayPrototype();
+    if (!arrayProto || arrayProto != proto) {
+      break;
+    }
+
+    // The array doesn't define an own @@iterator property.
+    jsid id = SYMBOL_TO_JSID(realm->runtime()->wellKnownSymbols().iterator);
+    bool res;
+    MOZ_TRY_VAR(res, testNotDefinedProperty(arr, id, true));
+    if (!res) {
+      break;
+    }
+
+    // Array.prototype[@@iterator] is not modified.
+    if (!ensureArrayPrototypeIteratorNotModified()) {
+      break;
+    }
+
+    // %ArrayIteratorPrototype%.next is not modified.
+    if (!ensureArrayIteratorPrototypeNextNotModified()) {
+      break;
+    }
+
+    result = true;
+  } while (false);
+
+  pushConstant(BooleanValue(result));
+  return Ok();
+}
+
 AbortReasonOr<Ok> IonBuilder::jsop_funapplyarray(uint32_t argc) {
   MOZ_ASSERT(argc == 2);
 
   int funcDepth = -((int)argc + 1);
 
   // Extract call target.
   TemporaryTypeSet* funTypes = current->peek(funcDepth)->resultTypeSet();
   JSFunction* target = getSingleCallTarget(funTypes);
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -212,16 +212,26 @@ class IonBuilder : public MIRGenerator,
   MDefinition* createThisScriptedBaseline(MDefinition* callee);
   MDefinition* createThis(JSFunction* target, MDefinition* callee,
                           MDefinition* newTarget);
   MInstruction* createNamedLambdaObject(MDefinition* callee,
                                         MDefinition* envObj);
   AbortReasonOr<MInstruction*> createCallObject(MDefinition* callee,
                                                 MDefinition* envObj);
 
+  // Returns true if a property hasn't been overwritten and matches the given
+  // predicate. Adds type constraints to ensure recompilation happens if the
+  // property value ever changes.
+  bool propertyIsConstantFunction(NativeObject* nobj, jsid id,
+                                  bool (*test)(IonBuilder* builder,
+                                               JSFunction* fun));
+
+  bool ensureArrayPrototypeIteratorNotModified();
+  bool ensureArrayIteratorPrototypeNextNotModified();
+
   MDefinition* walkEnvironmentChain(unsigned hops);
 
   MInstruction* addConvertElementsToDoubles(MDefinition* elements);
   MDefinition* addMaybeCopyElementsForWrite(MDefinition* object,
                                             bool checkNative);
 
   MInstruction* addBoundsCheck(MDefinition* index, MDefinition* length);
 
@@ -565,16 +575,17 @@ class IonBuilder : public MIRGenerator,
   AbortReasonOr<Ok> jsop_throwsetconst();
   AbortReasonOr<Ok> jsop_checklexical();
   AbortReasonOr<Ok> jsop_checkaliasedlexical(EnvironmentCoordinate ec);
   AbortReasonOr<Ok> jsop_funcall(uint32_t argc);
   AbortReasonOr<Ok> jsop_funapply(uint32_t argc);
   AbortReasonOr<Ok> jsop_funapplyarguments(uint32_t argc);
   AbortReasonOr<Ok> jsop_funapplyarray(uint32_t argc);
   AbortReasonOr<Ok> jsop_spreadcall();
+  AbortReasonOr<Ok> jsop_optimize_spreadcall();
   AbortReasonOr<Ok> jsop_call(uint32_t argc, bool constructing,
                               bool ignoresReturnValue);
   AbortReasonOr<Ok> jsop_eval(uint32_t argc);
   AbortReasonOr<Ok> jsop_label();
   AbortReasonOr<Ok> jsop_andor(JSOp op);
   AbortReasonOr<Ok> jsop_dup2();
   AbortReasonOr<Ok> jsop_loopentry();
   AbortReasonOr<Ok> jsop_loophead(jsbytecode* pc);
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -599,16 +599,24 @@ class GlobalObject : public NativeObject
   }
 
   static NativeObject* getOrCreateArrayIteratorPrototype(
       JSContext* cx, Handle<GlobalObject*> global) {
     return MaybeNativeObject(getOrCreateObject(cx, global, ARRAY_ITERATOR_PROTO,
                                                initArrayIteratorProto));
   }
 
+  NativeObject* maybeGetArrayIteratorPrototype() {
+    Value v = getSlotRef(ARRAY_ITERATOR_PROTO);
+    if (v.isObject()) {
+      return &v.toObject().as<NativeObject>();
+    }
+    return nullptr;
+  }
+
   static NativeObject* getOrCreateStringIteratorPrototype(
       JSContext* cx, Handle<GlobalObject*> global) {
     return MaybeNativeObject(getOrCreateObject(
         cx, global, STRING_ITERATOR_PROTO, initStringIteratorProto));
   }
 
   static NativeObject* getOrCreateRegExpStringIteratorPrototype(
       JSContext* cx, Handle<GlobalObject*> global) {