Narrow cases where Sparse/Smart versions of Array methods are used

Added a new %HasComplexElements runtime function (meaning elements that are
non-writable, non-configurable, or have getters and setters) and use it
in UseSparseVariant to filter out cases where the sparse optimizations
can cause V8 to fall out of spec compliance.

Renamed SmartMove/SmartSlice to SparseMove/SparseSlice and guarded them
with the new and improved UseSparseVariant.

These two changes combine let us pass nearly every test in bug-2615.js,
as well as fixing reverse and join on sparse arrays.

Note that there are various test changes in this patch that correct existing
tests to match the correct-by-spec behavior.

This patch depends on https://codereview.chromium.org/666883009, which
better-aligns the behavior of SmartMove with SimpleMove.

BUG=v8:2615,v8:3612,v8:3621
LOG=y
R=mstarzinger@chromium.org

Review URL: https://codereview.chromium.org/656423004

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@24855 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
adamk@chromium.org 2014-10-23 18:21:50 +00:00
parent f3c3697521
commit 5f1ae66d56
13 changed files with 181 additions and 112 deletions

View File

@ -90,7 +90,8 @@ function UseSparseVariant(array, length, is_array, touched) {
// Only use the sparse variant on arrays that are likely to be sparse and the // Only use the sparse variant on arrays that are likely to be sparse and the
// number of elements touched in the operation is relatively small compared to // number of elements touched in the operation is relatively small compared to
// the overall size of the array. // the overall size of the array.
if (!is_array || length < 1000 || %IsObserved(array)) { if (!is_array || length < 1000 || %IsObserved(array) ||
%HasComplexElements(array)) {
return false; return false;
} }
if (!%_IsSmi(length)) { if (!%_IsSmi(length)) {
@ -203,7 +204,7 @@ function ConvertToLocaleString(e) {
// This function implements the optimized splice implementation that can use // This function implements the optimized splice implementation that can use
// special array operations to handle sparse arrays in a sensible fashion. // special array operations to handle sparse arrays in a sensible fashion.
function SmartSlice(array, start_i, del_count, len, deleted_elements) { function SparseSlice(array, start_i, del_count, len, deleted_elements) {
// Move deleted elements to a new array (the return value from splice). // Move deleted elements to a new array (the return value from splice).
var indices = %GetArrayKeys(array, start_i + del_count); var indices = %GetArrayKeys(array, start_i + del_count);
if (IS_NUMBER(indices)) { if (IS_NUMBER(indices)) {
@ -233,7 +234,7 @@ function SmartSlice(array, start_i, del_count, len, deleted_elements) {
// This function implements the optimized splice implementation that can use // This function implements the optimized splice implementation that can use
// special array operations to handle sparse arrays in a sensible fashion. // special array operations to handle sparse arrays in a sensible fashion.
function SmartMove(array, start_i, del_count, len, num_additional_args) { function SparseMove(array, start_i, del_count, len, num_additional_args) {
// Bail out if no moving is necessary. // Bail out if no moving is necessary.
if (num_additional_args === del_count) return; if (num_additional_args === del_count) return;
// Move data to new array. // Move data to new array.
@ -608,8 +609,8 @@ function ArrayShift() {
var first = array[0]; var first = array[0];
if (IS_ARRAY(array)) { if (UseSparseVariant(array, len, IS_ARRAY(array), len)) {
SmartMove(array, 0, 1, len, 0); SparseMove(array, 0, 1, len, 0);
} else { } else {
SimpleMove(array, 0, 1, len, 0); SimpleMove(array, 0, 1, len, 0);
} }
@ -648,10 +649,10 @@ function ArrayUnshift(arg1) { // length == 1
var array = TO_OBJECT_INLINE(this); var array = TO_OBJECT_INLINE(this);
var len = TO_UINT32(array.length); var len = TO_UINT32(array.length);
var num_arguments = %_ArgumentsLength(); var num_arguments = %_ArgumentsLength();
var is_sealed = ObjectIsSealed(array);
if (IS_ARRAY(array) && !is_sealed && len > 0) { if (len > 0 && UseSparseVariant(array, len, IS_ARRAY(array), len) &&
SmartMove(array, 0, 0, len, num_arguments); !ObjectIsSealed(array)) {
SparseMove(array, 0, 0, len, num_arguments);
} else { } else {
SimpleMove(array, 0, 0, len, num_arguments); SimpleMove(array, 0, 0, len, num_arguments);
} }
@ -697,7 +698,7 @@ function ArraySlice(start, end) {
if (UseSparseVariant(array, len, IS_ARRAY(array), end_i - start_i)) { if (UseSparseVariant(array, len, IS_ARRAY(array), end_i - start_i)) {
%NormalizeElements(array); %NormalizeElements(array);
%NormalizeElements(result); %NormalizeElements(result);
SmartSlice(array, start_i, end_i - start_i, len, result); SparseSlice(array, start_i, end_i - start_i, len, result);
} else { } else {
SimpleSlice(array, start_i, end_i - start_i, len, result); SimpleSlice(array, start_i, end_i - start_i, len, result);
} }
@ -813,8 +814,8 @@ function ArraySplice(start, delete_count) {
if (UseSparseVariant(array, len, IS_ARRAY(array), changed_elements)) { if (UseSparseVariant(array, len, IS_ARRAY(array), changed_elements)) {
%NormalizeElements(array); %NormalizeElements(array);
%NormalizeElements(deleted_elements); %NormalizeElements(deleted_elements);
SmartSlice(array, start_i, del_count, len, deleted_elements); SparseSlice(array, start_i, del_count, len, deleted_elements);
SmartMove(array, start_i, del_count, len, num_elements_to_add); SparseMove(array, start_i, del_count, len, num_elements_to_add);
} else { } else {
SimpleSlice(array, start_i, del_count, len, deleted_elements); SimpleSlice(array, start_i, del_count, len, deleted_elements);
SimpleMove(array, start_i, del_count, len, num_elements_to_add); SimpleMove(array, start_i, del_count, len, num_elements_to_add);

View File

@ -14212,9 +14212,11 @@ template
int Dictionary<NameDictionary, NameDictionaryShape, Handle<Name> >:: int Dictionary<NameDictionary, NameDictionaryShape, Handle<Name> >::
NumberOfEnumElements(); NumberOfEnumElements();
template template bool Dictionary<SeededNumberDictionary, SeededNumberDictionaryShape,
int HashTable<SeededNumberDictionary, SeededNumberDictionaryShape, uint32_t>:: uint32_t>::HasComplexElements();
FindEntry(uint32_t);
template int HashTable<SeededNumberDictionary, SeededNumberDictionaryShape,
uint32_t>::FindEntry(uint32_t);
Handle<Object> JSObject::PrepareSlowElementsForSort( Handle<Object> JSObject::PrepareSlowElementsForSort(
@ -15262,10 +15264,26 @@ int Dictionary<Derived, Shape, Key>::NumberOfEnumElements() {
} }
template <typename Derived, typename Shape, typename Key>
bool Dictionary<Derived, Shape, Key>::HasComplexElements() {
int capacity = DerivedHashTable::Capacity();
for (int i = 0; i < capacity; i++) {
Object* k = DerivedHashTable::KeyAt(i);
if (DerivedHashTable::IsKey(k) && !FilterKey(k, NONE)) {
PropertyDetails details = DetailsAt(i);
if (details.IsDeleted()) continue;
if (details.type() == CALLBACKS) return true;
PropertyAttributes attr = details.attributes();
if (attr & (READ_ONLY | DONT_DELETE)) return true;
}
}
return false;
}
template <typename Derived, typename Shape, typename Key> template <typename Derived, typename Shape, typename Key>
void Dictionary<Derived, Shape, Key>::CopyKeysTo( void Dictionary<Derived, Shape, Key>::CopyKeysTo(
FixedArray* storage, FixedArray* storage, PropertyAttributes filter,
PropertyAttributes filter,
typename Dictionary<Derived, Shape, Key>::SortMode sort_mode) { typename Dictionary<Derived, Shape, Key>::SortMode sort_mode) {
DCHECK(storage->length() >= NumberOfElementsFilterAttributes(filter)); DCHECK(storage->length() >= NumberOfElementsFilterAttributes(filter));
int capacity = DerivedHashTable::Capacity(); int capacity = DerivedHashTable::Capacity();

View File

@ -3550,6 +3550,10 @@ class Dictionary: public HashTable<Derived, Shape, Key> {
// Returns the number of enumerable elements in the dictionary. // Returns the number of enumerable elements in the dictionary.
int NumberOfEnumElements(); int NumberOfEnumElements();
// Returns true if the dictionary contains any elements that are non-writable,
// non-configurable, or have getters/setters.
bool HasComplexElements();
enum SortMode { UNSORTED, SORTED }; enum SortMode { UNSORTED, SORTED };
// Copies keys to preallocated fixed array. // Copies keys to preallocated fixed array.
void CopyKeysTo(FixedArray* storage, void CopyKeysTo(FixedArray* storage,

View File

@ -1043,6 +1043,30 @@ RUNTIME_FUNCTION(Runtime_NormalizeElements) {
} }
RUNTIME_FUNCTION(Runtime_HasComplexElements) {
HandleScope scope(isolate);
DCHECK(args.length() == 1);
CONVERT_ARG_HANDLE_CHECKED(JSObject, array, 0);
for (PrototypeIterator iter(isolate, array,
PrototypeIterator::START_AT_RECEIVER);
!iter.IsAtEnd(); iter.Advance()) {
if (PrototypeIterator::GetCurrent(iter)->IsJSProxy()) {
return isolate->heap()->true_value();
}
Handle<JSObject> current =
Handle<JSObject>::cast(PrototypeIterator::GetCurrent(iter));
if (current->HasIndexedInterceptor()) {
return isolate->heap()->true_value();
}
if (!current->HasDictionaryElements()) continue;
if (current->element_dictionary()->HasComplexElements()) {
return isolate->heap()->true_value();
}
}
return isolate->heap()->false_value();
}
// TODO(dcarney): remove this function when TurboFan supports it. // TODO(dcarney): remove this function when TurboFan supports it.
// Takes the object to be iterated over and the result of GetPropertyNamesFast // Takes the object to be iterated over and the result of GetPropertyNamesFast
// Returns pair (cache_array, cache_type). // Returns pair (cache_array, cache_type).

View File

@ -268,6 +268,7 @@ namespace internal {
F(MoveArrayContents, 2, 1) \ F(MoveArrayContents, 2, 1) \
F(EstimateNumberOfElements, 1, 1) \ F(EstimateNumberOfElements, 1, 1) \
F(NormalizeElements, 1, 1) \ F(NormalizeElements, 1, 1) \
F(HasComplexElements, 1, 1) \
\ \
/* Getters and Setters */ \ /* Getters and Setters */ \
F(LookupAccessor, 3, 1) \ F(LookupAccessor, 3, 1) \

View File

@ -280,8 +280,7 @@ function array_natives_test() {
assertEquals([1.1,1,2,3], a4); assertEquals([1.1,1,2,3], a4);
a4 = [1.1,2,3]; a4 = [1.1,2,3];
a4.unshift(1); a4.unshift(1);
// assertTrue(%HasFastDoubleElements(a4)); assertTrue(%HasFastDoubleElements(a4));
assertTrue(%HasFastObjectElements(a4));
assertEquals([1,1.1,2,3], a4); assertEquals([1,1.1,2,3], a4);
a4 = [{},2,3]; a4 = [{},2,3];
a4.unshift(1); a4.unshift(1);

View File

@ -12,7 +12,17 @@ function test(array) {
array.shift(); array.shift();
return array; return array;
} }
assertEquals(["element 1",2], test(["0",,2]));
assertEquals(["element 1",{}], test([{},,{}])); var result = test(["0",,2]);
assertEquals(["element 1","element 1"], result);
assertTrue(result.hasOwnProperty("0"));
assertFalse(result.hasOwnProperty("1"));
result = test([{},,{}]);
assertEquals(["element 1","element 1"], result);
assertTrue(result.hasOwnProperty("0"));
assertFalse(result.hasOwnProperty("1"));
%OptimizeFunctionOnNextCall(test); %OptimizeFunctionOnNextCall(test);
assertEquals(["element 1",0], test([{},,0])); result = test([{},,0]);
assertEquals(["element 1","element 1"], result);
assertTrue(result.hasOwnProperty("0"));
assertFalse(result.hasOwnProperty("1"));

View File

@ -38,89 +38,3 @@ assertThrows("a.splice(1,1,7,7,7,7,7);", RangeError);
assertEquals([1,7,7,7,7,7,3], a.slice(0, 7)); assertEquals([1,7,7,7,7,7,3], a.slice(0, 7));
assertEquals(0xffffffff, a.length); assertEquals(0xffffffff, a.length);
assertEquals(10, a[0xfffffffe + 5 - 1]); assertEquals(10, a[0xfffffffe + 5 - 1]);
a = [1];
Object.defineProperty(a, "1", {writable:false, configurable:false, value: 100});
assertThrows("a.unshift(4);", TypeError);
assertEquals([1, 100, 100], a);
var desc = Object.getOwnPropertyDescriptor(a, "1");
assertEquals(false, desc.writable);
assertEquals(false, desc.configurable);
a = [1];
var g = function() { return 100; };
Object.defineProperty(a, "1", {get:g});
assertThrows("a.unshift(0);", TypeError);
assertEquals([1, 100, 100], a);
desc = Object.getOwnPropertyDescriptor(a, "1");
assertEquals(false, desc.configurable);
assertEquals(g, desc.get);
a = [1];
var c = 0;
var s = function(v) { c += 1; };
Object.defineProperty(a, "1", {set:s});
a.unshift(10);
assertEquals([10, undefined, undefined], a);
assertEquals(1, c);
desc = Object.getOwnPropertyDescriptor(a, "1");
assertEquals(false, desc.configurable);
assertEquals(s, desc.set);
a = [1];
Object.defineProperty(a, "1", {configurable:false, value:10});
assertThrows("a.splice(1,1);", TypeError);
assertEquals([1, 10], a);
desc = Object.getOwnPropertyDescriptor(a, "1");
assertEquals(false, desc.configurable);
a = [0,1,2,3,4,5,6];
Object.defineProperty(a, "3", {configurable:false, writable:false, value:3});
assertThrows("a.splice(1,4);", TypeError);
assertEquals([0,5,6,3,,,,,], a);
desc = Object.getOwnPropertyDescriptor(a, "3");
assertEquals(false, desc.configurable);
assertEquals(false, desc.writable);
a = [0,1,2,3,4,5,6];
Object.defineProperty(a, "5", {configurable:false, value:5});
assertThrows("a.splice(1,4);", TypeError);
assertEquals([0,5,6,3,4,5,,,], a);
desc = Object.getOwnPropertyDescriptor(a, "5");
assertEquals(false, desc.configurable);
a = [1,2,3,,5];
Object.defineProperty(a, "1", {configurable:false, writable:true, value:2});
assertEquals(1, a.shift());
assertEquals([2,3,,5], a);
desc = Object.getOwnPropertyDescriptor(a, "1");
assertEquals(false, desc.configurable);
assertEquals(true, desc.writable);
assertThrows("a.shift();", TypeError);
assertEquals([3,3,,5], a);
desc = Object.getOwnPropertyDescriptor(a, "1");
assertEquals(false, desc.configurable);
assertEquals(true, desc.writable);
a = [1,2,3];
Object.defineProperty(a, "2", {configurable:false, value:3});
assertThrows("a.pop();", TypeError);
assertEquals([1,2,3], a);
desc = Object.getOwnPropertyDescriptor(a, "2");
assertEquals(false, desc.configurable);
a = [1,2,,,5];
Object.defineProperty(a, "4", {writable:true, configurable:false, value:5});
assertThrows("a.sort();", TypeError);
assertEquals([1,2,5,,5], a);
desc = Object.getOwnPropertyDescriptor(a, "2");
assertEquals(true, desc.configurable);
desc = Object.getOwnPropertyDescriptor(a, "4");
assertEquals(false, desc.configurable);
a = [1,2,3,,5,6];
Object.defineProperty(a, "4", {value:5, writable:false});
assertThrows("a.sort();", TypeError);
assertEquals([1,2,3,5,5,6], a);
desc = Object.getOwnPropertyDescriptor(a, "4");
assertEquals(false, desc.writable);

View File

@ -0,0 +1,96 @@
// Copyright 2013 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
a = [1];
Object.defineProperty(a, "1", {writable:false, configurable:false, value: 100});
assertThrows("a.unshift(4);", TypeError);
assertEquals([1, 100, 100], a);
var desc = Object.getOwnPropertyDescriptor(a, "1");
assertEquals(false, desc.writable);
assertEquals(false, desc.configurable);
a = [1];
var g = function() { return 100; };
Object.defineProperty(a, "1", {get:g});
assertThrows("a.unshift(0);", TypeError);
assertEquals([1, 100, 100], a);
desc = Object.getOwnPropertyDescriptor(a, "1");
assertEquals(false, desc.configurable);
assertEquals(g, desc.get);
a = [1];
var c = 0;
var s = function(v) { c += 1; };
Object.defineProperty(a, "1", {set:s});
a.unshift(10);
assertEquals([10, undefined, undefined], a);
assertEquals(1, c);
desc = Object.getOwnPropertyDescriptor(a, "1");
assertEquals(false, desc.configurable);
assertEquals(s, desc.set);
a = [1];
Object.defineProperty(a, "1", {configurable:false, value:10});
assertThrows("a.splice(1,1);", TypeError);
assertEquals([1, 10], a);
desc = Object.getOwnPropertyDescriptor(a, "1");
assertEquals(false, desc.configurable);
a = [0,1,2,3,4,5,6];
Object.defineProperty(a, "3", {configurable:false, writable:false, value:3});
assertThrows("a.splice(1,4);", TypeError);
assertEquals([0,5,6,3,,,,], a);
desc = Object.getOwnPropertyDescriptor(a, "3");
assertEquals(false, desc.configurable);
assertEquals(false, desc.writable);
a = [0,1,2,3,4,5,6];
Object.defineProperty(a, "5", {configurable:false, value:5});
assertThrows("a.splice(1,4);", TypeError);
assertEquals([0,5,6,3,4,5,,], a);
desc = Object.getOwnPropertyDescriptor(a, "5");
assertEquals(false, desc.configurable);
a = [1,2,3,,5];
Object.defineProperty(a, "1", {configurable:false, writable:true, value:2});
assertEquals(1, a.shift());
assertEquals([2,3,,5], a);
desc = Object.getOwnPropertyDescriptor(a, "1");
assertEquals(false, desc.configurable);
assertEquals(true, desc.writable);
assertThrows("a.shift();", TypeError);
assertEquals([3,3,,5], a);
desc = Object.getOwnPropertyDescriptor(a, "1");
assertEquals(false, desc.configurable);
assertEquals(true, desc.writable);
a = [1,2,3];
Object.defineProperty(a, "2", {configurable:false, value:3});
assertThrows("a.pop();", TypeError);
assertEquals([1,2,3], a);
desc = Object.getOwnPropertyDescriptor(a, "2");
assertEquals(false, desc.configurable);

View File

@ -15,5 +15,5 @@ __f_6();
%OptimizeFunctionOnNextCall(__f_6); %OptimizeFunctionOnNextCall(__f_6);
__f_6(); __f_6();
function __f_7(__v_7) { function __f_7(__v_7) {
__v_7.push(Infinity); __v_7.pop();
} }

View File

@ -117,6 +117,11 @@
'js1_5/GC/regress-348532': [SKIP], 'js1_5/GC/regress-348532': [SKIP],
# Runs for too long: huge array with getters and setters. As it says
# in the test: "This test will probably run out of memory".
'js1_5/extensions/regress-345967': [SKIP],
##################### FLAKY TESTS ##################### ##################### FLAKY TESTS #####################
# These tests time out in debug mode but pass in product mode # These tests time out in debug mode but pass in product mode
@ -674,7 +679,6 @@
'js1_5/extensions/regress-311792-01': [FAIL_OK], 'js1_5/extensions/regress-311792-01': [FAIL_OK],
'js1_5/extensions/regress-312278': [FAIL_OK], 'js1_5/extensions/regress-312278': [FAIL_OK],
'js1_5/extensions/regress-313630': [FAIL_OK], 'js1_5/extensions/regress-313630': [FAIL_OK],
'js1_5/extensions/regress-313763': [FAIL_OK],
'js1_5/extensions/regress-313803': [FAIL_OK], 'js1_5/extensions/regress-313803': [FAIL_OK],
'js1_5/extensions/regress-314874': [FAIL_OK], 'js1_5/extensions/regress-314874': [FAIL_OK],
'js1_5/extensions/regress-322957': [FAIL_OK], 'js1_5/extensions/regress-322957': [FAIL_OK],
@ -684,8 +688,6 @@
'js1_5/extensions/regress-336409-1': [FAIL_OK], 'js1_5/extensions/regress-336409-1': [FAIL_OK],
'js1_5/extensions/regress-336409-2': [FAIL_OK], 'js1_5/extensions/regress-336409-2': [FAIL_OK],
'js1_5/extensions/regress-336410-2': [FAIL_OK], 'js1_5/extensions/regress-336410-2': [FAIL_OK],
'js1_5/extensions/regress-341956-01': [FAIL_OK],
'js1_5/extensions/regress-345967': [FAIL_OK],
'js1_5/extensions/regress-346494-01': [FAIL_OK], 'js1_5/extensions/regress-346494-01': [FAIL_OK],
'js1_5/extensions/regress-346494': [FAIL_OK], 'js1_5/extensions/regress-346494': [FAIL_OK],
'js1_5/extensions/regress-347306-02': [FAIL_OK], 'js1_5/extensions/regress-347306-02': [FAIL_OK],