Commit bf0913b2 authored by Hai Dang's avatar Hai Dang Committed by Commit Bot

[turbofan] Add JSCallReducer for Array#slice in case of cloning.

Direct call to CloneFastJSArray is used to improve performance in that
case. Tests are also added.

Bug: v8:7980
Change-Id: Ifca34f3e182b776cd9862da8bf529fc13f6be9ed
Reviewed-on: https://chromium-review.googlesource.com/1172782Reviewed-by: 's avatarSigurd Schneider <sigurds@chromium.org>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Commit-Queue: Hai Dang <dhai@google.com>
Cr-Commit-Position: refs/heads/master@{#55154}
parent d732d35a
......@@ -3447,6 +3447,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node,
return ReduceArrayPrototypePop(node);
case Builtins::kArrayPrototypeShift:
return ReduceArrayPrototypeShift(node);
case Builtins::kArrayPrototypeSlice:
return ReduceArrayPrototypeSlice(node);
case Builtins::kArrayPrototypeEntries:
return ReduceArrayIterator(node, IterationKind::kEntries);
case Builtins::kArrayPrototypeKeys:
......@@ -4769,6 +4771,85 @@ Reduction JSCallReducer::ReduceArrayPrototypeShift(Node* node) {
return Replace(value);
}
// ES6 section 22.1.3.23 Array.prototype.slice ( )
Reduction JSCallReducer::ReduceArrayPrototypeSlice(Node* node) {
if (!FLAG_turbo_inline_array_builtins) return NoChange();
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& p = CallParametersOf(node->op());
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
int arity = static_cast<int>(p.arity() - 2);
// Here we only optimize for cloning, that is when slice is called
// without arguments, or with a single argument that is the constant 0.
if (arity >= 2) return NoChange();
if (arity == 1) {
NumberMatcher m(NodeProperties::GetValueInput(node, 2));
if (!m.HasValue()) return NoChange();
if (m.Value() != 0) return NoChange();
}
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Try to determine the {receiver} map.
ZoneHandleSet<Map> receiver_maps;
NodeProperties::InferReceiverMapsResult result =
NodeProperties::InferReceiverMaps(isolate(), receiver, effect,
&receiver_maps);
if (result == NodeProperties::kNoReceiverMaps) return NoChange();
// Ensure that any changes to the Array species constructor cause deopt.
if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange();
dependencies()->DependOnProtector(
PropertyCellRef(js_heap_broker(), factory()->array_species_protector()));
bool can_be_holey = false;
// Check that the maps are of JSArray (and more)
for (Handle<Map> receiver_map : receiver_maps) {
if (!CanInlineArrayIteratingBuiltin(isolate(), receiver_map))
return NoChange();
if (IsHoleyElementsKind(receiver_map->elements_kind())) can_be_holey = true;
}
// Install code dependency on the array protector for holey arrays.
if (can_be_holey) {
dependencies()->DependOnProtector(
PropertyCellRef(js_heap_broker(), factory()->no_elements_protector()));
}
// If we have unreliable maps, we need a map check.
// This is actually redundant due to how JSNativeContextSpecialization
// reduces the load of slice, but we do it here nevertheless for consistency
// and robustness.
if (result == NodeProperties::kUnreliableReceiverMaps) {
effect =
graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone,
receiver_maps, p.feedback()),
receiver, effect, control);
}
Node* context = NodeProperties::GetContextInput(node);
Callable callable =
Builtins::CallableFor(isolate(), Builtins::kCloneFastJSArray);
auto call_descriptor = Linkage::GetStubCallDescriptor(
graph()->zone(), callable.descriptor(), 0, CallDescriptor::kNoFlags,
Operator::kNoThrow | Operator::kNoDeopt);
// Calls to Builtins::kCloneFastJSArray produce COW arrays
// if the original array is COW
Node* clone = effect = graph()->NewNode(
common()->Call(call_descriptor), jsgraph()->HeapConstant(callable.code()),
receiver, context, effect, control);
ReplaceWithValue(node, clone, effect, control);
return Replace(clone);
}
// ES6 section 22.1.2.2 Array.isArray ( arg )
Reduction JSCallReducer::ReduceArrayIsArray(Node* node) {
// We certainly know that undefined is not an array.
......
......@@ -94,6 +94,7 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
Reduction ReduceArrayPrototypePush(Node* node);
Reduction ReduceArrayPrototypePop(Node* node);
Reduction ReduceArrayPrototypeShift(Node* node);
Reduction ReduceArrayPrototypeSlice(Node* node);
Reduction ReduceArrayIsArray(Node* node);
enum class ArrayIteratorKind { kArray, kTypedArray };
Reduction ReduceArrayIterator(Node* node, IterationKind kind);
......
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --opt
// Test CloneFastJSArray inserted by JSCallReducer for Array.prototype.slice.
// CloneFastJSArray produces COW arrays if the original array is COW.
// Trigger JSCallReducer on slice() and slice(0)
(function() {
const arr = [1,2,3,4,5];
function slice() {
return arr.slice();
}
function slice0() {
return arr.slice(0);
}
assertEquals(arr, slice());
assertFalse(arr === slice());
assertEquals(slice(), slice0());
assertEquals(slice0(), slice());
%OptimizeFunctionOnNextCall(slice0);
%OptimizeFunctionOnNextCall(slice);
assertEquals(slice(), slice0());
assertOptimized(slice); assertOptimized(slice0);
})();
// This will cause deopt of slice by a CheckMap installed by
// JSNativeContextSpecialization::ReduceNamedAccess
(function() {
const arr = [1,2,3,4,5];
function slice() {
return arr.slice();
}
assertEquals(arr, slice());
assertEquals(slice(), arr);
%OptimizeFunctionOnNextCall(slice);
slice();
// Trigger deopt here
arr.push(7.2);
assertEquals(slice()[5], 7.2);
})();
// There should not be a deopt cycle.
(function() {
const arr = [1,2,3,4,5];
function slice() {
return arr.slice();
}
assertEquals(arr, slice());
assertEquals(slice(), arr);
%OptimizeFunctionOnNextCall(slice);
// Trigger opt
assertEquals(slice(), arr);
// Trigger deopt by CheckMap from JSNativeContextSpecialization
arr.push(7.2);
slice();
%OptimizeFunctionOnNextCall(slice);
// Trigger opt again
slice();
// Should not deopt again
arr.push(8.2);
slice();
assertOptimized(slice);
})();
// JSCallReducer will not reduce because the species has been modified
(function() {
const array = [3,4,5];
function slice(){
return array.slice();
}
class MyArray extends Array {};
array.constructor = MyArray;
slice(); slice();
%OptimizeFunctionOnNextCall(slice);
var narr = slice();
assertInstanceof(narr, MyArray);
})();
(function() {
const array = [3,4,5];
function slice(){
return array.slice();
}
slice(); slice();
%OptimizeFunctionOnNextCall(slice);
slice();
class MyArray extends Array {};
array.constructor = MyArray;
// deopt
var narr = slice();
// if not deopt, narr will be instanceof Array
assertTrue(narr instanceof MyArray);
})();
// JSCallReducer adds check for UnreliableReceiverMaps
(function() {
const arr = [1,2,3,4,5];
function slice() {
return arr.slice();
}
slice(); slice();
arr.foo = 6.2;
%OptimizeFunctionOnNextCall(slice);
// JSCallReducer will add check for UnreliableReceiverMaps
slice();
// Trigger deopt because of DependOnStableMaps
// installed by JSNativeContextSpecialization,
// but not the check installed by ReduceArrayPrototypeSlice itself
arr.bar = 7.2;
let narr = slice();
assertEquals(arr, narr);
assertEquals(narr.foo, undefined);
assertEquals(narr.bar, undefined);
})();
// Multiple maps
(function() {
const iarr = [1,2,3];
const darr = [2.1, 3.3, 0.2];
function slice(arr) {
return arr.slice();
}
slice(iarr); slice(darr);
slice(iarr); slice(darr);
%OptimizeFunctionOnNextCall(slice);
// The optimization works for both maps
assertEquals(iarr, slice(iarr));
assertEquals(darr, slice(darr));
assertOptimized(slice);
})();
// Tests for the branch of CanInlineArrayIteratingBuiltin
// JSCallReducer will not reduce to CloneFastJSArray
// if array's prototype is not JS_ARRAY_TYPE
(function () {
class MyArray extends Array {
constructor() {
super();
this[6]= 6;
}
}
let array = new MyArray(3, 5, 4);
function slice() {
return array.slice();
}
assertEquals(slice(),array);
slice();
%OptimizeFunctionOnNextCall(slice);
let narr = slice();
// here, slice supposes to call MyArray's constructor.
// If we optimize with CloneFastJSArray, Array's constructor is called instead.
assertEquals(narr[6], 6);
assertTrue(narr instanceof MyArray);
})();
// JSCallReducer will not reduce to CloneFastJSArray
// if array's instance type is not JS_ARRAY_TYPE.
// CloneFastJSArray does not work with non JS_ARRAY_TYPE.
// Check : receiver_map->instance_type() == JS_ARRAY_TYPE
(function () {
var x = {"0" : 0, "2": 2} ;
x.__proto__ = Array.prototype;
function slice() {
return x.slice();
}
slice(); slice();
%OptimizeFunctionOnNextCall(slice);
assertEquals(slice(), []);
})();
// JSCallReducer will not reduce to CloneFastJSArray
// since array is not Fast Elements Kind
// Check : IsFastElementsKind(receiver_map->elements_kind())
(function () {
var array = [3, 4, 5];
function slice() {
return array.slice();
}
assertEquals(slice(),array);
slice();
// a sparse array switches to Dictionary Elements
array[9999] = 0;
%OptimizeFunctionOnNextCall(slice);
var narr = slice();
assertEquals(narr, array);
})();
(function () {
var array = [3, 4, 5];
function slice() {
return array.slice();
}
assertEquals(slice(),array);
slice();
%OptimizeFunctionOnNextCall(slice);
slice();
// a sparse array switches to Dictionary Elements
array[9999] = 0;
// trigger deopt because map changes
assertEquals(slice(),array);
})();
// JSCallReducer will not reduce to CloneFastJSArray
// if array is used as a prototype and has unstable map
(function () {
var array = [3, 5, 4];
function slice(arr) {
return arr.slice();
}
// make array's map is_prototype_map()
var x = {__proto__ : array};
assertEquals(slice(array),array);
slice(array);
// make array's map unstable
array.push(6.3);
slice(array);
%OptimizeFunctionOnNextCall(slice);
assertEquals(slice(array),array);
})();
// JSCallReducer will not reduce to CloneFastJSArray
// if the Array prototype got some elements.
// Check: isolate->IsNoElementsProtectorIntact()
(function () {
var array = [, 6, 6];
function slice() {
return array.slice();
}
assertEquals(slice(),array);
slice();
array.__proto__.push(6);
%OptimizeFunctionOnNextCall(slice);
// if we optimized, we would get [ , 6, 6]
// here, slice copies elements from both the object and the prototype
let narr = slice();
assertNotEquals(Object.getOwnPropertyDescriptor(narr,0), undefined);
assertEquals(narr, [6, 6, 6]);
})();
(function () {
var array = [, 6, 6];
function slice() {
return array.slice();
}
assertEquals(slice(),array);
slice();
%OptimizeFunctionOnNextCall(slice);
slice();
// Deopt
array.__proto__.push(6);
let narr = slice();
assertNotEquals(Object.getOwnPropertyDescriptor(narr, 0), undefined);
assertEquals(narr[0], 6);
})();
// JSCallReducer will not reduce to CloneFastJSArray
// if the Array prototype is not original
// Check: isolate->IsAnyInitialArrayPrototype(receiver_prototype)
(function () {
var array = [6, , 6];
function slice() {
return array.slice();
}
assertEquals(slice(),array);
slice();
// change the prototype
array.__proto__ = [ , 6, ];
%OptimizeFunctionOnNextCall(slice);
let narr = slice();
// if optimized, we would get [6, , 6]
assertNotEquals(Object.getOwnPropertyDescriptor(narr, 1), undefined);
assertEquals(narr, [6,6,6]);
})();
(function () {
var array = [6, ,6];
function slice() {
return array.slice();
}
assertEquals(slice(),array);
slice();
%OptimizeFunctionOnNextCall(slice);
slice();
// change the prototype
array.__proto__ = [,6,];
// deopt because of map changed
let narr = slice();
// if optimized, we would get [6, , 6]
assertNotEquals(Object.getOwnPropertyDescriptor(narr, 1), undefined);
assertEquals(narr, [6,6,6]);
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment