From 109673303b7f1dd718d96e5d8f485fbc3d295382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bargull?= Date: Thu, 18 Nov 2021 11:37:29 +0000 Subject: [PATCH] Bug 1740737 - Part 2: Handle arguments in optimised spread calls. r=iain When the `arguments` object is still in its initial state (no overridden elements, length, or iterator), `...arguments` can be optimised to directly create the spread array from the arguments' content instead of going through the iterator code. The CacheIR code additionally disallows overridden or forwarded arguments in order to further optimise this operation during scalar replacement. See part 4. Differential Revision: https://phabricator.services.mozilla.com/D130987 --- .../external-arguments-inlined-spread.js | 14 ++ .../external-arguments-spread-forwarded.js | 18 ++ .../arguments/external-arguments-spread.js | 17 ++ ...guments-escaped-new-spread-optimization.js | 50 ++++ ...e-arguments-escaped-spread-optimization.js | 50 ++++ ...ments-new-spread-optimization-rectifier.js | 39 ++++ ...nline-arguments-new-spread-optimization.js | 47 ++++ ...arguments-spread-optimization-rectifier.js | 39 ++++ .../inline-arguments-spread-optimization.js | 47 ++++ .../tests/arguments/spread-args-obj-01.js | 18 ++ .../tests/arguments/spread-args-obj-02.js | 16 ++ .../tests/arguments/spread-args-obj-03.js | 20 ++ .../tests/arguments/spread-args-obj-04.js | 21 ++ .../arguments/spread-call-optimization.js | 216 ++++++++++++++++++ .../spread-closed-over-arguments-strict.js | 15 ++ .../arguments/spread-closed-over-arguments.js | 14 ++ .../tests/arguments/spread-redefine-length.js | 18 ++ js/src/jit/CacheIR.cpp | 58 +++++ js/src/jit/CacheIR.h | 1 + js/src/jit/CacheIRCompiler.cpp | 16 ++ js/src/jit/CacheIROps.yaml | 7 + js/src/jit/VMFunctionList-inl.h | 1 + js/src/vm/Interpreter.cpp | 115 ++++++++-- js/src/vm/Interpreter.h | 3 + 24 files changed, 846 insertions(+), 14 deletions(-) create mode 100644 js/src/jit-test/tests/arguments/external-arguments-inlined-spread.js create mode 100644 js/src/jit-test/tests/arguments/external-arguments-spread-forwarded.js create mode 100644 js/src/jit-test/tests/arguments/external-arguments-spread.js create mode 100644 js/src/jit-test/tests/arguments/inline-arguments-escaped-new-spread-optimization.js create mode 100644 js/src/jit-test/tests/arguments/inline-arguments-escaped-spread-optimization.js create mode 100644 js/src/jit-test/tests/arguments/inline-arguments-new-spread-optimization-rectifier.js create mode 100644 js/src/jit-test/tests/arguments/inline-arguments-new-spread-optimization.js create mode 100644 js/src/jit-test/tests/arguments/inline-arguments-spread-optimization-rectifier.js create mode 100644 js/src/jit-test/tests/arguments/inline-arguments-spread-optimization.js create mode 100644 js/src/jit-test/tests/arguments/spread-args-obj-01.js create mode 100644 js/src/jit-test/tests/arguments/spread-args-obj-02.js create mode 100644 js/src/jit-test/tests/arguments/spread-args-obj-03.js create mode 100644 js/src/jit-test/tests/arguments/spread-args-obj-04.js create mode 100644 js/src/jit-test/tests/arguments/spread-call-optimization.js create mode 100644 js/src/jit-test/tests/arguments/spread-closed-over-arguments-strict.js create mode 100644 js/src/jit-test/tests/arguments/spread-closed-over-arguments.js create mode 100644 js/src/jit-test/tests/arguments/spread-redefine-length.js diff --git a/js/src/jit-test/tests/arguments/external-arguments-inlined-spread.js b/js/src/jit-test/tests/arguments/external-arguments-inlined-spread.js new file mode 100644 index 000000000000..d4be0829bd10 --- /dev/null +++ b/js/src/jit-test/tests/arguments/external-arguments-inlined-spread.js @@ -0,0 +1,14 @@ +function foo() { + return bar(arguments); +} +function bar(argsFromFoo) { + return baz(...argsFromFoo) +} +function baz(a,b) { return a + b; } + +var sum = 0; +with ({}) {} +for (var i = 0; i < 100; i++) { + sum += foo(1,2); +} +assertEq(sum, 300); diff --git a/js/src/jit-test/tests/arguments/external-arguments-spread-forwarded.js b/js/src/jit-test/tests/arguments/external-arguments-spread-forwarded.js new file mode 100644 index 000000000000..7a70189c28a1 --- /dev/null +++ b/js/src/jit-test/tests/arguments/external-arguments-spread-forwarded.js @@ -0,0 +1,18 @@ +function foo(x,y) { + function capture() { return x; } + return bar(arguments); +} + +function bar(x) { + return baz(...x) + arguments.length; +} + +function baz(x,y) { + return x + y; +} + +var sum = 0; +for (var i = 0; i < 100; i++) { + sum += foo(1,2); +} +assertEq(sum, 400) diff --git a/js/src/jit-test/tests/arguments/external-arguments-spread.js b/js/src/jit-test/tests/arguments/external-arguments-spread.js new file mode 100644 index 000000000000..cc0f1e18f181 --- /dev/null +++ b/js/src/jit-test/tests/arguments/external-arguments-spread.js @@ -0,0 +1,17 @@ +function foo(x,y) { + return bar(arguments); +} + +function bar(x) { + return baz(...x) + arguments.length; +} + +function baz(x,y) { + return x + y; +} + +var sum = 0; +for (var i = 0; i < 100; i++) { + sum += foo(1,2); +} +assertEq(sum, 400) diff --git a/js/src/jit-test/tests/arguments/inline-arguments-escaped-new-spread-optimization.js b/js/src/jit-test/tests/arguments/inline-arguments-escaped-new-spread-optimization.js new file mode 100644 index 000000000000..e57633906904 --- /dev/null +++ b/js/src/jit-test/tests/arguments/inline-arguments-escaped-new-spread-optimization.js @@ -0,0 +1,50 @@ +// |jit-test| --fast-warmup + +function foo(...args) { + with ({}) {} + return {result: args.length}; +} + +function inner() { + return arguments; +} + +function outer0() { + trialInline(); + var args = inner(); + return new foo(...args); +} + +function outer1() { + trialInline(); + var args = inner(1); + return new foo(...args); +} + +function outer2() { + trialInline(); + var args = inner(1,2); + return new foo(...args); +} + +function outer3() { + trialInline(); + var args = inner(1,2,3); + return new foo(...args); +} + +function outer4() { + trialInline(); + var args = inner(1,2,3,4); + return new foo(...args); +} + +with ({}) {} + +for (var i = 0; i < 50; i++) { + assertEq(outer0().result, 0); + assertEq(outer1().result, 1); + assertEq(outer2().result, 2); + assertEq(outer3().result, 3); + assertEq(outer4().result, 4); +} diff --git a/js/src/jit-test/tests/arguments/inline-arguments-escaped-spread-optimization.js b/js/src/jit-test/tests/arguments/inline-arguments-escaped-spread-optimization.js new file mode 100644 index 000000000000..a5092339e376 --- /dev/null +++ b/js/src/jit-test/tests/arguments/inline-arguments-escaped-spread-optimization.js @@ -0,0 +1,50 @@ +// |jit-test| --fast-warmup + +function foo(...args) { + with ({}) {} + return args.length; +} + +function inner() { + return arguments; +} + +function outer0() { + trialInline(); + var args = inner(); + return foo(...args); +} + +function outer1() { + trialInline(); + var args = inner(1); + return foo(...args); +} + +function outer2() { + trialInline(); + var args = inner(1,2); + return foo(...args); +} + +function outer3() { + trialInline(); + var args = inner(1,2,3); + return foo(...args); +} + +function outer4() { + trialInline(); + var args = inner(1,2,3,4); + return foo(...args); +} + +with ({}) {} + +for (var i = 0; i < 50; i++) { + assertEq(outer0(), 0); + assertEq(outer1(), 1); + assertEq(outer2(), 2); + assertEq(outer3(), 3); + assertEq(outer4(), 4); +} diff --git a/js/src/jit-test/tests/arguments/inline-arguments-new-spread-optimization-rectifier.js b/js/src/jit-test/tests/arguments/inline-arguments-new-spread-optimization-rectifier.js new file mode 100644 index 000000000000..6aa15d876fe9 --- /dev/null +++ b/js/src/jit-test/tests/arguments/inline-arguments-new-spread-optimization-rectifier.js @@ -0,0 +1,39 @@ +// |jit-test| --fast-warmup + +function foo(a,b) { + with ({}) {} + return {result: a + b}; +} + +function inner(x,y) { + assertEq(x + y, new foo(...arguments).result); +} + +function outer0() { + trialInline(); + inner(); +} + +function outer1() { + trialInline(); + inner(1); +} + +function outer2() { + trialInline(); + inner(1, 2); +} + +function outer3() { + trialInline(); + inner(1,2,3); +} + +with ({}) {} + +for (var i = 0; i < 50; i++) { + outer0(); + outer1(); + outer2(); + outer3(); +} diff --git a/js/src/jit-test/tests/arguments/inline-arguments-new-spread-optimization.js b/js/src/jit-test/tests/arguments/inline-arguments-new-spread-optimization.js new file mode 100644 index 000000000000..2dab581e6852 --- /dev/null +++ b/js/src/jit-test/tests/arguments/inline-arguments-new-spread-optimization.js @@ -0,0 +1,47 @@ +// |jit-test| --fast-warmup + + +function foo(...args) { + with ({}) {} + return {result: args.length}; +} + +function inner() { + // Single argument spread calls with |arguments| can be optimised. + return new foo(...arguments); +} + +function outer0() { + trialInline(); + return inner(); +} + +function outer1() { + trialInline(); + return inner(1); +} + +function outer2() { + trialInline(); + return inner(1, 2); +} + +function outer3() { + trialInline(); + return inner(1,2,3) +} + +function outer4() { + trialInline(); + return inner(1,2,3,4) +} + +with ({}) {} + +for (var i = 0; i < 50; i++) { + assertEq(outer0().result, 0); + assertEq(outer1().result, 1); + assertEq(outer2().result, 2); + assertEq(outer3().result, 3); + assertEq(outer4().result, 4); +} diff --git a/js/src/jit-test/tests/arguments/inline-arguments-spread-optimization-rectifier.js b/js/src/jit-test/tests/arguments/inline-arguments-spread-optimization-rectifier.js new file mode 100644 index 000000000000..a89abcdc0beb --- /dev/null +++ b/js/src/jit-test/tests/arguments/inline-arguments-spread-optimization-rectifier.js @@ -0,0 +1,39 @@ +// |jit-test| --fast-warmup + +function foo(a,b) { + with ({}) {} + return a + b; +} + +function inner(x,y) { + assertEq(x + y, foo(...arguments)); +} + +function outer0() { + trialInline(); + inner(); +} + +function outer1() { + trialInline(); + inner(1); +} + +function outer2() { + trialInline(); + inner(1, 2); +} + +function outer3() { + trialInline(); + inner(1,2,3); +} + +with ({}) {} + +for (var i = 0; i < 50; i++) { + outer0(); + outer1(); + outer2(); + outer3(); +} diff --git a/js/src/jit-test/tests/arguments/inline-arguments-spread-optimization.js b/js/src/jit-test/tests/arguments/inline-arguments-spread-optimization.js new file mode 100644 index 000000000000..21505e943764 --- /dev/null +++ b/js/src/jit-test/tests/arguments/inline-arguments-spread-optimization.js @@ -0,0 +1,47 @@ +// |jit-test| --fast-warmup + + +function foo(...args) { + with ({}) {} + return args.length; +} + +function inner() { + // Single argument spread calls with |arguments| can be optimised. + return foo(...arguments); +} + +function outer0() { + trialInline(); + return inner(); +} + +function outer1() { + trialInline(); + return inner(1); +} + +function outer2() { + trialInline(); + return inner(1, 2); +} + +function outer3() { + trialInline(); + return inner(1,2,3) +} + +function outer4() { + trialInline(); + return inner(1,2,3,4) +} + +with ({}) {} + +for (var i = 0; i < 50; i++) { + assertEq(outer0(), 0); + assertEq(outer1(), 1); + assertEq(outer2(), 2); + assertEq(outer3(), 3); + assertEq(outer4(), 4); +} diff --git a/js/src/jit-test/tests/arguments/spread-args-obj-01.js b/js/src/jit-test/tests/arguments/spread-args-obj-01.js new file mode 100644 index 000000000000..2528922a4a2b --- /dev/null +++ b/js/src/jit-test/tests/arguments/spread-args-obj-01.js @@ -0,0 +1,18 @@ +function escape(x) { with ({}) {} } + +function foo() { + escape(arguments); + return bar(...arguments); +} + +function bar(x,y) { + return x + y; +} + +with ({}) {} + +var sum = 0; +for (var i = 0; i < 100; i++) { + sum += foo(1,2); +} +assertEq(sum, 300); diff --git a/js/src/jit-test/tests/arguments/spread-args-obj-02.js b/js/src/jit-test/tests/arguments/spread-args-obj-02.js new file mode 100644 index 000000000000..6bb14851c52d --- /dev/null +++ b/js/src/jit-test/tests/arguments/spread-args-obj-02.js @@ -0,0 +1,16 @@ +function foo() { + arguments[0] = 3; + return bar(...arguments); +} + +function bar(x,y) { + return x + y; +} + +with ({}) {} + +var sum = 0; +for (var i = 0; i < 100; i++) { + sum += foo(1,2); +} +assertEq(sum, 500); diff --git a/js/src/jit-test/tests/arguments/spread-args-obj-03.js b/js/src/jit-test/tests/arguments/spread-args-obj-03.js new file mode 100644 index 000000000000..702884272aa0 --- /dev/null +++ b/js/src/jit-test/tests/arguments/spread-args-obj-03.js @@ -0,0 +1,20 @@ +function escape() { with ({}) {} } + +function foo(i) { + return i; +} + +function bar(n) { + escape(arguments); + return foo(...arguments); +} + +function baz(a, n) { + return bar(n); +} + +var sum = 0; +for (var i = 0; i < 10000; i++) { + sum += baz(0, 1); +} +assertEq(sum, 10000); diff --git a/js/src/jit-test/tests/arguments/spread-args-obj-04.js b/js/src/jit-test/tests/arguments/spread-args-obj-04.js new file mode 100644 index 000000000000..b72322a36523 --- /dev/null +++ b/js/src/jit-test/tests/arguments/spread-args-obj-04.js @@ -0,0 +1,21 @@ +var result; + +function g(a, b) { + with ({}) {} + result = a + b; +} + +function escape() { with({}) {} } + +function f() { + escape(arguments); + for (var i = 0; i < 50; ++i) { + g(...arguments); + } +} + +f(1, 2); +assertEq(result, 3); + +f(""); +assertEq(result, "undefined"); diff --git a/js/src/jit-test/tests/arguments/spread-call-optimization.js b/js/src/jit-test/tests/arguments/spread-call-optimization.js new file mode 100644 index 000000000000..285f9d89a437 --- /dev/null +++ b/js/src/jit-test/tests/arguments/spread-call-optimization.js @@ -0,0 +1,216 @@ +// Tests when |arguments| are used in optimized spread calls. + +function testBasic() { + // Return the number of arguments. + function argslen() { return arguments.length; } + + // Return the first argument. + function arg0() { return arguments[0]; } + + // Return the argument at the request index. + function argIndex(i) { return arguments[i]; } + + // Call the above functions when no formals are present. + function noFormalsLen() { return argslen(...arguments); } + function noFormals0() { return arg0(...arguments); } + function noFormalsIndex() { return argIndex(...arguments); } + + // Call the above functions when some formals are present. + function formalsLen(x, y, z) { return argslen(...arguments); } + function formals0(x, y, z) { return arg0(...arguments); } + function formalsIndex(x, y, z) { return argIndex(...arguments); } + + // Call the above functions when a rest argument is present. + function restLen(...rest) { return argslen(...arguments); } + function rest0(...rest) { return arg0(...arguments); } + function restIndex(...rest) { return argIndex(...arguments); } + + // Call the above functions when default parameters are present. + function defaultLen(x = 0) { return argslen(...arguments); } + function default0(x = 0) { return arg0(...arguments); } + function defaultIndex(x = 0) { return argIndex(...arguments); } + + for (let i = 0; i < 100; ++i) { + assertEq(noFormalsLen(), 0); + assertEq(noFormalsLen(1), 1); + assertEq(noFormalsLen(1, 2, 3), 3); + + assertEq(formalsLen(), 0); + assertEq(formalsLen(1), 1); + assertEq(formalsLen(1, 2, 3), 3); + + assertEq(restLen(), 0); + assertEq(restLen(1), 1); + assertEq(restLen(1, 2, 3), 3); + + assertEq(defaultLen(), 0); + assertEq(defaultLen(1), 1); + assertEq(defaultLen(1, 2, 3), 3); + + assertEq(noFormals0(), undefined); + assertEq(noFormals0(100), 100); + assertEq(noFormals0(100, 200, 300), 100); + + assertEq(formals0(), undefined); + assertEq(formals0(100), 100); + assertEq(formals0(100, 200, 300), 100); + + assertEq(rest0(), undefined); + assertEq(rest0(100), 100); + assertEq(rest0(100, 200, 300), 100); + + assertEq(default0(), undefined); + assertEq(default0(100), 100); + assertEq(default0(100, 200, 300), 100); + + assertEq(noFormalsIndex(), undefined); + assertEq(noFormalsIndex(0), 0); + assertEq(noFormalsIndex(0, 100), 0); + assertEq(noFormalsIndex(0, 100, 200, 300), 0); + assertEq(noFormalsIndex(1, 100), 100); + assertEq(noFormalsIndex(1, 100, 200, 300), 100); + + assertEq(formalsIndex(), undefined); + assertEq(formalsIndex(0), 0); + assertEq(formalsIndex(0, 100), 0); + assertEq(formalsIndex(0, 100, 200, 300), 0); + assertEq(formalsIndex(1, 100), 100); + assertEq(formalsIndex(1, 100, 200, 300), 100); + + assertEq(restIndex(), undefined); + assertEq(restIndex(0), 0); + assertEq(restIndex(0, 100), 0); + assertEq(restIndex(0, 100, 200, 300), 0); + assertEq(restIndex(1, 100), 100); + assertEq(restIndex(1, 100, 200, 300), 100); + + assertEq(defaultIndex(), undefined); + assertEq(defaultIndex(0), 0); + assertEq(defaultIndex(0, 100), 0); + assertEq(defaultIndex(0, 100, 200, 300), 0); + assertEq(defaultIndex(1, 100), 100); + assertEq(defaultIndex(1, 100, 200, 300), 100); + } +} +//testBasic(); + +function testOverriddenIterator() { + function g(x) { + return x; + } + + function f(i) { + if (i === 100) { + arguments[Symbol.iterator] = function() { + return ["bad"].values(); + }; + } + return g(...arguments); + } + + for (let i = 0; i <= 150; ++i) { + assertEq(f(i), i !== 100 ? i : "bad"); + } +} +testOverriddenIterator(); + +function testOverriddenLength() { + function g(x, y = 0) { + return x + y; + } + + function f(i) { + if (i === 100) { + arguments.length = 1; + } + return g(...arguments); + } + + for (let i = 0; i <= 150; ++i) { + assertEq(f(i, i), i !== 100 ? i * 2 : i); + } +} +testOverriddenLength(); + +function testOverriddenElement() { + function g(x, y) { + return x + y; + } + + function f(i) { + if (i === 100) { + arguments[1] = 0; + } + return g(...arguments); + } + + for (let i = 0; i <= 150; ++i) { + assertEq(f(i, i), i !== 100 ? i * 2 : i); + } +} +testOverriddenElement(); + +function testDeletedElement() { + function g(x, y = 0) { + return x + y; + } + + function f(i) { + if (i === 100) { + delete arguments[1]; + } + return g(...arguments); + } + + for (let i = 0; i <= 150; ++i) { + assertEq(f(i, i), i !== 100 ? i * 2 : i); + } +} +testDeletedElement(); + +function testForwardedArg() { + function g(x, y) { + return x + y; + } + + function f(i) { + function closedOver() { + if (i === 100) i = 0; + } + closedOver(); + return g(...arguments); + } + + for (let i = 0; i <= 150; ++i) { + assertEq(f(i, i), i !== 100 ? i * 2 : i); + } +} +testForwardedArg(); + +function testModifiedArrayIteratorPrototype() { + function g(x, y) { + return x + y; + } + + const ArrayIteratorPrototype = Reflect.getPrototypeOf([][Symbol.iterator]()); + const ArrayIteratorPrototypeNext = ArrayIteratorPrototype.next; + function newNext() { + var result = ArrayIteratorPrototypeNext.call(this); + if (!result.done) { + result.value *= 2; + } + return result; + } + + function f(i) { + if (i === 100) { + ArrayIteratorPrototype.next = newNext; + } + return g(...arguments); + } + + for (let i = 0; i <= 150; ++i) { + assertEq(f(i, i), i < 100 ? i * 2 : i * 4); + } +} +testModifiedArrayIteratorPrototype(); diff --git a/js/src/jit-test/tests/arguments/spread-closed-over-arguments-strict.js b/js/src/jit-test/tests/arguments/spread-closed-over-arguments-strict.js new file mode 100644 index 000000000000..05ce6ec8317c --- /dev/null +++ b/js/src/jit-test/tests/arguments/spread-closed-over-arguments-strict.js @@ -0,0 +1,15 @@ +'use strict' +function bar(x,y) { + return x + y; +} + +function foo(x, y) { + function closeOver() { return x; } + return bar(...arguments); +} + +var sum = 0; +for (var i = 0; i < 100; i++) { + sum += foo(1,2); +} +assertEq(sum, 300) diff --git a/js/src/jit-test/tests/arguments/spread-closed-over-arguments.js b/js/src/jit-test/tests/arguments/spread-closed-over-arguments.js new file mode 100644 index 000000000000..15dd800ae395 --- /dev/null +++ b/js/src/jit-test/tests/arguments/spread-closed-over-arguments.js @@ -0,0 +1,14 @@ +function bar(x,y) { + return x + y; +} + +function foo(x, y) { + function closeOver() { return x; } + return bar(...arguments); +} + +var sum = 0; +for (var i = 0; i < 100; i++) { + sum += foo(1,2); +} +assertEq(sum, 300) diff --git a/js/src/jit-test/tests/arguments/spread-redefine-length.js b/js/src/jit-test/tests/arguments/spread-redefine-length.js new file mode 100644 index 000000000000..8c62ddd5d9f4 --- /dev/null +++ b/js/src/jit-test/tests/arguments/spread-redefine-length.js @@ -0,0 +1,18 @@ +function foo() { + arguments.length = 2; + return bar(...arguments); +} + +function bar() { + var result = 0; + for (var x of arguments) { + result += x; + } + return result; +} + +var sum = 0; +for (var i = 0; i < 100; i++) { + sum += foo(1,2,3,4,5,6); +} +assertEq(sum,300); diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp index e92025b71aa0..bf9949b9db55 100644 --- a/js/src/jit/CacheIR.cpp +++ b/js/src/jit/CacheIR.cpp @@ -4937,6 +4937,7 @@ AttachDecision OptimizeSpreadCallIRGenerator::tryAttachStub() { AutoAssertNoPendingException aanpe(cx_); TRY_ATTACH(tryAttachArray()); + TRY_ATTACH(tryAttachArguments()); TRY_ATTACH(tryAttachNotOptimizable()); trackAttached(IRGenerator::NotAttached); @@ -5065,6 +5066,63 @@ AttachDecision OptimizeSpreadCallIRGenerator::tryAttachArray() { return AttachDecision::Attach; } +AttachDecision OptimizeSpreadCallIRGenerator::tryAttachArguments() { + // The value must be an arguments object. + if (!val_.isObject()) { + return AttachDecision::NoAction; + } + JSObject* obj = &val_.toObject(); + if (!obj->is()) { + return AttachDecision::NoAction; + } + auto* args = &obj->as(); + + // Ensure neither elements, nor the length, nor the iterator has been + // overridden. Also ensure no args are forwarded to allow reading them + // directly from the frame. + if (args->hasOverriddenElement() || args->hasOverriddenLength() || + args->hasOverriddenIterator() || args->anyArgIsForwarded()) { + return AttachDecision::NoAction; + } + + NativeObject* arrayIteratorProto; + uint32_t slot; + JSFunction* nextFun; + if (!IsArrayIteratorPrototypeOptimizable(cx_, &arrayIteratorProto, &slot, + &nextFun)) { + return AttachDecision::NoAction; + } + + ValOperandId valId(writer.setInputOperandId(0)); + ObjOperandId objId = writer.guardToObject(valId); + + if (args->is()) { + writer.guardClass(objId, GuardClassKind::MappedArguments); + } else { + MOZ_ASSERT(args->is()); + writer.guardClass(objId, GuardClassKind::UnmappedArguments); + } + uint8_t flags = ArgumentsObject::ELEMENT_OVERRIDDEN_BIT | + ArgumentsObject::LENGTH_OVERRIDDEN_BIT | + ArgumentsObject::ITERATOR_OVERRIDDEN_BIT | + ArgumentsObject::FORWARDED_ARGUMENTS_BIT; + writer.guardArgumentsObjectFlags(objId, flags); + + ObjOperandId protoId = writer.loadObject(arrayIteratorProto); + ObjOperandId nextId = writer.loadObject(nextFun); + + writer.guardShape(protoId, arrayIteratorProto->shape()); + + // Ensure that proto[slot] == nextFun. + writer.guardDynamicSlotIsSpecificObject(protoId, nextId, slot); + + writer.arrayFromArgumentsObjectResult(objId); + writer.returnFromIC(); + + trackAttached("Arguments"); + return AttachDecision::Attach; +} + AttachDecision OptimizeSpreadCallIRGenerator::tryAttachNotOptimizable() { ValOperandId valId(writer.setInputOperandId(0)); diff --git a/js/src/jit/CacheIR.h b/js/src/jit/CacheIR.h index 5d05063b34a3..d6667f172e48 100644 --- a/js/src/jit/CacheIR.h +++ b/js/src/jit/CacheIR.h @@ -1619,6 +1619,7 @@ class MOZ_RAII OptimizeSpreadCallIRGenerator : public IRGenerator { HandleValue val_; AttachDecision tryAttachArray(); + AttachDecision tryAttachArguments(); AttachDecision tryAttachNotOptimizable(); public: diff --git a/js/src/jit/CacheIRCompiler.cpp b/js/src/jit/CacheIRCompiler.cpp index c269eb688d93..a1942c01c50b 100644 --- a/js/src/jit/CacheIRCompiler.cpp +++ b/js/src/jit/CacheIRCompiler.cpp @@ -41,6 +41,7 @@ #include "vm/FunctionFlags.h" // js::FunctionFlags #include "vm/GeneratorObject.h" #include "vm/GetterSetter.h" +#include "vm/Interpreter.h" #include "vm/Uint8Clamped.h" #include "builtin/Boolean-inl.h" @@ -8558,6 +8559,21 @@ bool CacheIRCompiler::emitMapGetObjectResult(ObjOperandId mapId, return true; } +bool CacheIRCompiler::emitArrayFromArgumentsObjectResult(ObjOperandId objId) { + JitSpew(JitSpew_Codegen, "%s", __FUNCTION__); + + AutoCallVM callvm(masm, this, allocator); + + Register obj = allocator.useRegister(masm, objId); + + callvm.prepare(); + masm.Push(obj); + + using Fn = ArrayObject* (*)(JSContext*, Handle); + callvm.call(); + return true; +} + bool CacheIRCompiler::emitBailout() { JitSpew(JitSpew_Codegen, "%s", __FUNCTION__); diff --git a/js/src/jit/CacheIROps.yaml b/js/src/jit/CacheIROps.yaml index c9b1155762a3..535b64f58266 100644 --- a/js/src/jit/CacheIROps.yaml +++ b/js/src/jit/CacheIROps.yaml @@ -2780,6 +2780,13 @@ map: ObjId obj: ObjId +- name: ArrayFromArgumentsObjectResult + shared: true + transpile: false + cost_estimate: 5 + args: + obj: ObjId + - name: CallPrintString shared: true transpile: false diff --git a/js/src/jit/VMFunctionList-inl.h b/js/src/jit/VMFunctionList-inl.h index aced51aa7797..79ee1ed9a67e 100644 --- a/js/src/jit/VMFunctionList-inl.h +++ b/js/src/jit/VMFunctionList-inl.h @@ -40,6 +40,7 @@ namespace jit { js::ArgumentsObject::createForInlinedIon) \ _(ArgumentsObjectCreateForIon, js::ArgumentsObject::createForIon) \ _(ArrayConstructorOneArg, js::ArrayConstructorOneArg) \ + _(ArrayFromArgumentsObject, js::ArrayFromArgumentsObject) \ _(ArrayJoin, js::jit::ArrayJoin) \ _(ArrayPushDense, js::jit::ArrayPushDense) \ _(ArraySliceDense, js::ArraySliceDense) \ diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 2c9b02618149..81fa062172f8 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -66,6 +66,7 @@ #include "builtin/Boolean-inl.h" #include "debugger/DebugAPI-inl.h" +#include "vm/ArgumentsObject-inl.h" #include "vm/EnvironmentObject-inl.h" #include "vm/GeckoProfiler-inl.h" #include "vm/JSAtom-inl.h" @@ -4991,8 +4992,10 @@ bool js::SpreadCallOperation(JSContext* cx, HandleScript script, jsbytecode* pc, return true; } -bool js::OptimizeSpreadCall(JSContext* cx, HandleValue arg, - MutableHandleValue result) { +static bool OptimizeArraySpreadCall(JSContext* cx, HandleObject obj, + MutableHandleValue result) { + MOZ_ASSERT(result.isUndefined()); + // Optimize spread call by skipping spread operation when following // conditions are met: // * the argument is an array @@ -5001,14 +5004,7 @@ bool js::OptimizeSpreadCall(JSContext* cx, HandleValue arg, // * the array's prototype is Array.prototype // * Array.prototype[@@iterator] is not modified // * %ArrayIteratorPrototype%.next is not modified - if (!arg.isObject()) { - result.setUndefined(); - return true; - } - - RootedObject obj(cx, &arg.toObject()); if (!IsPackedArray(obj)) { - result.setUndefined(); return true; } @@ -5021,15 +5017,106 @@ bool js::OptimizeSpreadCall(JSContext* cx, HandleValue arg, if (!stubChain->tryOptimizeArray(cx, obj.as(), &optimized)) { return false; } - - if (optimized) { - result.setObject(*obj); - } else { - result.setUndefined(); + if (!optimized) { + return true; } + + result.setObject(*obj); return true; } +static bool OptimizeArgumentsSpreadCall(JSContext* cx, HandleObject obj, + MutableHandleValue result) { + MOZ_ASSERT(result.isUndefined()); + + // Optimize spread call by skipping the spread operation when the following + // conditions are met: + // * the argument is an arguments object + // * the arguments object has no deleted elements + // * arguments.length is not overridden + // * arguments[@@iterator] is not overridden + // * %ArrayIteratorPrototype%.next is not modified + + if (!obj->is()) { + return true; + } + + Handle args = obj.as(); + if (args->isAnyElementDeleted() || args->hasOverriddenLength() || + args->hasOverriddenIterator()) { + return true; + } + + ForOfPIC::Chain* stubChain = ForOfPIC::getOrCreate(cx); + if (!stubChain) { + return false; + } + + bool optimized; + if (!stubChain->tryOptimizeArrayIteratorNext(cx, &optimized)) { + return false; + } + if (!optimized) { + return true; + } + + auto* array = ArrayFromArgumentsObject(cx, args); + if (!array) { + return false; + } + + result.setObject(*array); + return true; +} + +bool js::OptimizeSpreadCall(JSContext* cx, HandleValue arg, + MutableHandleValue result) { + // This function returns |undefined| if the spread operation can't be + // optimized. + result.setUndefined(); + + if (!arg.isObject()) { + return true; + } + + RootedObject obj(cx, &arg.toObject()); + if (!OptimizeArraySpreadCall(cx, obj, result)) { + return false; + } + if (result.isObject()) { + return true; + } + if (!OptimizeArgumentsSpreadCall(cx, obj, result)) { + return false; + } + if (result.isObject()) { + return true; + } + + MOZ_ASSERT(result.isUndefined()); + return true; +} + +ArrayObject* js::ArrayFromArgumentsObject(JSContext* cx, + Handle args) { + MOZ_ASSERT(!args->hasOverriddenLength()); + MOZ_ASSERT(!args->isAnyElementDeleted()); + + uint32_t length = args->initialLength(); + auto* array = NewDenseFullyAllocatedArray(cx, length); + if (!array) { + return nullptr; + } + array->setDenseInitializedLength(length); + + for (uint32_t index = 0; index < length; index++) { + const Value& v = args->element(index); + array->initDenseElement(index, v); + } + + return array; +} + JSObject* js::NewObjectOperation(JSContext* cx, HandleScript script, const jsbytecode* pc) { if (JSOp(*pc) == JSOp::NewObject) { diff --git a/js/src/vm/Interpreter.h b/js/src/vm/Interpreter.h index 250a12d51317..05db7d2f7035 100644 --- a/js/src/vm/Interpreter.h +++ b/js/src/vm/Interpreter.h @@ -616,6 +616,9 @@ bool SpreadCallOperation(JSContext* cx, HandleScript script, jsbytecode* pc, bool OptimizeSpreadCall(JSContext* cx, HandleValue arg, MutableHandleValue result); +ArrayObject* ArrayFromArgumentsObject(JSContext* cx, + Handle args); + JSObject* NewObjectOperation(JSContext* cx, HandleScript script, const jsbytecode* pc);