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);