Commit d3583574 authored by verwaest's avatar verwaest Committed by Commit bot

Replace PushIfAbsent by a Stack object

This significantly speeds up String(array).
BUG=

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

Cr-Commit-Position: refs/heads/master@{#34745}
parent 421a67b0
...@@ -73,17 +73,13 @@ function DefineIndexedProperty(array, i, value) { ...@@ -73,17 +73,13 @@ function DefineIndexedProperty(array, i, value) {
} }
} }
function KeySortCompare(a, b) {
return a - b;
}
// Global list of arrays visited during toString, toLocaleString and
// join invocations.
var visited_arrays = new InternalArray();
// Gets a sorted array of array keys. Useful for operations on sparse
// arrays. Dupes have not been removed.
function GetSortedArrayKeys(array, indices) { function GetSortedArrayKeys(array, indices) {
var keys = new InternalArray();
if (IS_NUMBER(indices)) { if (IS_NUMBER(indices)) {
var keys = new InternalArray();
// It's an interval // It's an interval
var limit = indices; var limit = indices;
for (var i = 0; i < limit; ++i) { for (var i = 0; i < limit; ++i) {
...@@ -92,61 +88,33 @@ function GetSortedArrayKeys(array, indices) { ...@@ -92,61 +88,33 @@ function GetSortedArrayKeys(array, indices) {
keys.push(i); keys.push(i);
} }
} }
} else {
var length = indices.length;
for (var k = 0; k < length; ++k) {
var key = indices[k];
if (!IS_UNDEFINED(key)) {
var e = array[key];
if (!IS_UNDEFINED(e) || key in array) {
keys.push(key);
}
}
}
keys.sort(function(a, b) { return a - b; });
} }
return keys; return InnerArraySort(indices, indices.length, KeySortCompare);
} }
function SparseJoinWithSeparatorJS(array, len, convert, separator) { function SparseJoinWithSeparatorJS(array, keys, length, convert, separator) {
var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len)); var keys_length = keys.length;
var totalLength = 0; var elements = new InternalArray(keys_length * 2);
var elements = new InternalArray(keys.length * 2); for (var i = 0; i < keys_length; i++) {
var previousKey = -1;
for (var i = 0; i < keys.length; i++) {
var key = keys[i]; var key = keys[i];
if (key != previousKey) { // keys may contain duplicates. var e = array[key];
var e = array[key]; elements[i * 2] = key;
if (!IS_STRING(e)) e = convert(e); elements[i * 2 + 1] = IS_STRING(e) ? e : convert(e);
elements[i * 2] = key;
elements[i * 2 + 1] = e;
previousKey = key;
}
} }
return %SparseJoinWithSeparator(elements, len, separator); return %SparseJoinWithSeparator(elements, length, separator);
} }
// Optimized for sparse arrays if separator is ''. // Optimized for sparse arrays if separator is ''.
function SparseJoin(array, len, convert) { function SparseJoin(array, keys, convert) {
var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
var last_key = -1;
var keys_length = keys.length; var keys_length = keys.length;
var elements = new InternalArray(keys_length); var elements = new InternalArray(keys_length);
var elements_length = 0;
for (var i = 0; i < keys_length; i++) { for (var i = 0; i < keys_length; i++) {
var key = keys[i]; var e = array[keys[i]];
if (key != last_key) { elements[i] = IS_STRING(e) ? e : convert(e);
var e = array[key];
if (!IS_STRING(e)) e = convert(e);
elements[elements_length++] = e;
last_key = key;
}
} }
return %StringBuilderConcat(elements, elements_length, ''); return %StringBuilderConcat(elements, keys_length, '');
} }
...@@ -167,98 +135,122 @@ function UseSparseVariant(array, length, is_array, touched) { ...@@ -167,98 +135,122 @@ function UseSparseVariant(array, length, is_array, touched) {
(touched > estimated_elements * 4); (touched > estimated_elements * 4);
} }
function Stack() {
this.length = 0;
this.values = new InternalArray();
}
function Join(array, length, separator, convert) { // Predeclare the instance variables on the prototype. Otherwise setting them in
if (length == 0) return ''; // the constructor will leak the instance through settings on Object.prototype.
Stack.prototype.length = null;
Stack.prototype.values = null;
var is_array = IS_ARRAY(array); function StackPush(stack, value) {
stack.values[stack.length++] = value;
}
if (is_array) { function StackPop(stack) {
// If the array is cyclic, return the empty string for already stack.values[--stack.length] = null
// visited arrays. }
if (!%PushIfAbsent(visited_arrays, array)) return '';
function StackHas(stack, v) {
var length = stack.length;
var values = stack.values;
for (var i = 0; i < length; i++) {
if (values[i] === v) return true;
} }
return false;
}
// Attempt to convert the elements. // Global list of arrays visited during toString, toLocaleString and
try { // join invocations.
if (UseSparseVariant(array, length, is_array, length)) { var visited_arrays = new Stack();
%NormalizeElements(array);
if (separator.length == 0) {
return SparseJoin(array, length, convert);
} else {
return SparseJoinWithSeparatorJS(array, length, convert, separator);
}
}
// Fast case for one-element arrays. function DoJoin(array, length, is_array, separator, convert) {
if (length == 1) { if (UseSparseVariant(array, length, is_array, length)) {
var e = array[0]; %NormalizeElements(array);
if (IS_STRING(e)) return e; var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, length));
return convert(e); if (separator === '') {
if (keys.length === 0) return '';
return SparseJoin(array, keys, convert);
} else {
return SparseJoinWithSeparatorJS(array, keys, length, convert, separator);
} }
}
// Construct an array for the elements. // Fast case for one-element arrays.
var elements = new InternalArray(length); if (length === 1) {
var e = array[0];
return IS_STRING(e) ? e : convert(e);
}
// We pull the empty separator check outside the loop for speed! // Construct an array for the elements.
if (separator.length == 0) { var elements = new InternalArray(length);
var elements_length = 0;
for (var i = 0; i < length; i++) { // We pull the empty separator check outside the loop for speed!
var e = array[i]; if (separator === '') {
if (!IS_STRING(e)) e = convert(e); for (var i = 0; i < length; i++) {
elements[elements_length++] = e; var e = array[i];
} elements[i] = IS_STRING(e) ? e : convert(e);
elements.length = elements_length;
return %StringBuilderConcat(elements, elements_length, '');
} }
// Non-empty separator case. return %StringBuilderConcat(elements, length, '');
// If the first element is a number then use the heuristic that the }
// remaining elements are also likely to be numbers. // Non-empty separator case.
var e = array[0]; // If the first element is a number then use the heuristic that the
if (!IS_NUMBER(e)) { // remaining elements are also likely to be numbers.
if (!IS_STRING(e)) e = convert(e); var e = array[0];
elements[0] = e; if (IS_NUMBER(e)) {
for (var i = 1; i < length; i++) { elements[0] = %_NumberToString(e);
e = array[i]; for (var i = 1; i < length; i++) {
if (!IS_STRING(e)) e = convert(e); e = array[i];
elements[i] = e; if (IS_NUMBER(e)) {
} elements[i] = %_NumberToString(e);
} else { } else {
elements[0] = %_NumberToString(e); elements[i] = IS_STRING(e) ? e : convert(e);
for (var i = 1; i < length; i++) {
e = array[i];
if (IS_NUMBER(e)) {
e = %_NumberToString(e);
} else if (!IS_STRING(e)) {
e = convert(e);
}
elements[i] = e;
} }
} }
return %StringBuilderJoin(elements, length, separator); } else {
elements[0] = IS_STRING(e) ? e : convert(e);
for (var i = 1; i < length; i++) {
e = array[i];
elements[i] = IS_STRING(e) ? e : convert(e);
}
}
return %StringBuilderJoin(elements, length, separator);
}
function Join(array, length, separator, convert) {
if (length === 0) return '';
var is_array = IS_ARRAY(array);
if (is_array) {
// If the array is cyclic, return the empty string for already
// visited arrays.
if (StackHas(visited_arrays, array)) return '';
StackPush(visited_arrays, array);
}
// Attempt to convert the elements.
try {
return DoJoin(array, length, is_array, separator, convert);
} finally { } finally {
// Make sure to remove the last element of the visited array no // Make sure to remove the last element of the visited array no
// matter what happens. // matter what happens.
if (is_array) visited_arrays.length = visited_arrays.length - 1; if (is_array) StackPop(visited_arrays);
} }
} }
function ConvertToString(x) { function ConvertToString(x) {
if (IS_NULL_OR_UNDEFINED(x)) { if (IS_NULL_OR_UNDEFINED(x)) return '';
return ''; return TO_STRING(x);
} else {
return TO_STRING(x);
}
} }
function ConvertToLocaleString(e) { function ConvertToLocaleString(e) {
if (IS_NULL_OR_UNDEFINED(e)) { if (IS_NULL_OR_UNDEFINED(e)) return '';
return ''; return TO_STRING(e.toLocaleString());
} else {
return TO_STRING(e.toLocaleString());
}
} }
...@@ -1959,6 +1951,10 @@ utils.Export(function(to) { ...@@ -1959,6 +1951,10 @@ utils.Export(function(to) {
to.InnerArraySort = InnerArraySort; to.InnerArraySort = InnerArraySort;
to.InnerArrayToLocaleString = InnerArrayToLocaleString; to.InnerArrayToLocaleString = InnerArrayToLocaleString;
to.PackedArrayReverse = PackedArrayReverse; to.PackedArrayReverse = PackedArrayReverse;
to.Stack = Stack;
to.StackHas = StackHas;
to.StackPush = StackPush;
to.StackPop = StackPop;
}); });
%InstallToContext([ %InstallToContext([
......
...@@ -19,6 +19,10 @@ var MakeTypeError; ...@@ -19,6 +19,10 @@ var MakeTypeError;
var MaxSimple; var MaxSimple;
var MinSimple; var MinSimple;
var ObjectHasOwnProperty; var ObjectHasOwnProperty;
var Stack;
var StackHas;
var StackPop;
var StackPush;
var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol"); var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol");
utils.Import(function(from) { utils.Import(function(from) {
...@@ -26,6 +30,10 @@ utils.Import(function(from) { ...@@ -26,6 +30,10 @@ utils.Import(function(from) {
MaxSimple = from.MaxSimple; MaxSimple = from.MaxSimple;
MinSimple = from.MinSimple; MinSimple = from.MinSimple;
ObjectHasOwnProperty = from.ObjectHasOwnProperty; ObjectHasOwnProperty = from.ObjectHasOwnProperty;
Stack = from.Stack;
StackHas = from.StackHas;
StackPop = from.StackPop;
StackPush = from.StackPush;
}); });
// ------------------------------------------------------------------- // -------------------------------------------------------------------
...@@ -78,7 +86,8 @@ function JSONParse(text, reviver) { ...@@ -78,7 +86,8 @@ function JSONParse(text, reviver) {
function SerializeArray(value, replacer, stack, indent, gap) { function SerializeArray(value, replacer, stack, indent, gap) {
if (!%PushIfAbsent(stack, value)) throw MakeTypeError(kCircularStructure); if (StackHas(stack, value)) throw MakeTypeError(kCircularStructure);
StackPush(stack, value);
var stepback = indent; var stepback = indent;
indent += gap; indent += gap;
var partial = new InternalArray(); var partial = new InternalArray();
...@@ -101,13 +110,14 @@ function SerializeArray(value, replacer, stack, indent, gap) { ...@@ -101,13 +110,14 @@ function SerializeArray(value, replacer, stack, indent, gap) {
} else { } else {
final = "[]"; final = "[]";
} }
stack.pop(); StackPop(stack);
return final; return final;
} }
function SerializeObject(value, replacer, stack, indent, gap) { function SerializeObject(value, replacer, stack, indent, gap) {
if (!%PushIfAbsent(stack, value)) throw MakeTypeError(kCircularStructure); if (StackHas(stack, value)) throw MakeTypeError(kCircularStructure);
StackPush(stack, value);
var stepback = indent; var stepback = indent;
indent += gap; indent += gap;
var partial = new InternalArray(); var partial = new InternalArray();
...@@ -146,7 +156,7 @@ function SerializeObject(value, replacer, stack, indent, gap) { ...@@ -146,7 +156,7 @@ function SerializeObject(value, replacer, stack, indent, gap) {
} else { } else {
final = "{}"; final = "{}";
} }
stack.pop(); StackPop(stack);
return final; return final;
} }
...@@ -241,7 +251,7 @@ function JSONStringify(value, replacer, space) { ...@@ -241,7 +251,7 @@ function JSONStringify(value, replacer, space) {
if (!IS_CALLABLE(replacer) && !property_list && !gap && !IS_PROXY(value)) { if (!IS_CALLABLE(replacer) && !property_list && !gap && !IS_PROXY(value)) {
return %BasicJSONStringify(value); return %BasicJSONStringify(value);
} }
return JSONSerialize('', {'': value}, replacer, new InternalArray(), "", gap); return JSONSerialize('', {'': value}, replacer, new Stack(), "", gap);
} }
// ------------------------------------------------------------------- // -------------------------------------------------------------------
...@@ -279,7 +289,7 @@ function JsonSerializeAdapter(key, object) { ...@@ -279,7 +289,7 @@ function JsonSerializeAdapter(key, object) {
var holder = {}; var holder = {};
holder[key] = object; holder[key] = object;
// No need to pass the actual holder since there is no replacer function. // No need to pass the actual holder since there is no replacer function.
return JSONSerialize(key, holder, UNDEFINED, new InternalArray(), "", ""); return JSONSerialize(key, holder, UNDEFINED, new Stack(), "", "");
} }
%InstallToContext(["json_serialize_adapter", JsonSerializeAdapter]); %InstallToContext(["json_serialize_adapter", JsonSerializeAdapter]);
......
...@@ -17482,9 +17482,6 @@ template Handle<NameDictionary> ...@@ -17482,9 +17482,6 @@ template Handle<NameDictionary>
Dictionary<NameDictionary, NameDictionaryShape, Handle<Name> >:: Dictionary<NameDictionary, NameDictionaryShape, Handle<Name> >::
EnsureCapacity(Handle<NameDictionary>, int, Handle<Name>); EnsureCapacity(Handle<NameDictionary>, int, Handle<Name>);
template bool Dictionary<SeededNumberDictionary, SeededNumberDictionaryShape,
uint32_t>::HasComplexElements();
template int HashTable<SeededNumberDictionary, SeededNumberDictionaryShape, template int HashTable<SeededNumberDictionary, SeededNumberDictionaryShape,
uint32_t>::FindEntry(uint32_t); uint32_t>::FindEntry(uint32_t);
...@@ -18325,6 +18322,21 @@ void Dictionary<Derived, Shape, Key>::AddEntry( ...@@ -18325,6 +18322,21 @@ void Dictionary<Derived, Shape, Key>::AddEntry(
dictionary->ElementAdded(); dictionary->ElementAdded();
} }
bool SeededNumberDictionary::HasComplexElements() {
if (!requires_slow_elements()) return false;
int capacity = this->Capacity();
for (int i = 0; i < capacity; i++) {
Object* k = this->KeyAt(i);
if (this->IsKey(k)) {
DCHECK(!IsDeleted(i));
PropertyDetails details = this->DetailsAt(i);
if (details.type() == ACCESSOR_CONSTANT) return true;
PropertyAttributes attr = details.attributes();
if (attr & ALL_ATTRIBUTES_MASK) return true;
}
}
return false;
}
void SeededNumberDictionary::UpdateMaxNumberKey(uint32_t key, void SeededNumberDictionary::UpdateMaxNumberKey(uint32_t key,
bool used_as_prototype) { bool used_as_prototype) {
...@@ -18432,23 +18444,6 @@ int Dictionary<Derived, Shape, Key>::NumberOfElementsFilterAttributes( ...@@ -18432,23 +18444,6 @@ int Dictionary<Derived, Shape, Key>::NumberOfElementsFilterAttributes(
} }
template <typename Derived, typename Shape, typename Key>
bool Dictionary<Derived, Shape, Key>::HasComplexElements() {
int capacity = this->Capacity();
for (int i = 0; i < capacity; i++) {
Object* k = this->KeyAt(i);
if (this->IsKey(k) && !k->FilterKey(ALL_PROPERTIES)) {
if (this->IsDeleted(i)) continue;
PropertyDetails details = this->DetailsAt(i);
if (details.type() == ACCESSOR_CONSTANT) return true;
PropertyAttributes attr = details.attributes();
if (attr & ALL_ATTRIBUTES_MASK) return true;
}
}
return false;
}
template <typename Dictionary> template <typename Dictionary>
struct EnumIndexComparator { struct EnumIndexComparator {
explicit EnumIndexComparator(Dictionary* dict) : dict(dict) {} explicit EnumIndexComparator(Dictionary* dict) : dict(dict) {}
......
...@@ -3485,10 +3485,6 @@ class Dictionary: public HashTable<Derived, Shape, Key> { ...@@ -3485,10 +3485,6 @@ class Dictionary: public HashTable<Derived, Shape, Key> {
return NumberOfElementsFilterAttributes(ENUMERABLE_STRINGS); return NumberOfElementsFilterAttributes(ENUMERABLE_STRINGS);
} }
// Returns true if the dictionary contains any elements that are non-writable,
// non-configurable, non-enumerable, or have getters/setters.
bool HasComplexElements();
enum SortMode { UNSORTED, SORTED }; enum SortMode { UNSORTED, SORTED };
// Fill in details for properties into storage. // Fill in details for properties into storage.
...@@ -3721,6 +3717,10 @@ class SeededNumberDictionary ...@@ -3721,6 +3717,10 @@ class SeededNumberDictionary
void UpdateMaxNumberKey(uint32_t key, bool used_as_prototype); void UpdateMaxNumberKey(uint32_t key, bool used_as_prototype);
// Returns true if the dictionary contains any elements that are non-writable,
// non-configurable, non-enumerable, or have getters/setters.
bool HasComplexElements();
// If slow elements are required we will never go back to fast-case // If slow elements are required we will never go back to fast-case
// for the elements kept in this dictionary. We require slow // for the elements kept in this dictionary. We require slow
// elements if an element has been added at an index larger than // elements if an element has been added at an index larger than
......
...@@ -88,29 +88,6 @@ RUNTIME_FUNCTION(Runtime_TransitionElementsKind) { ...@@ -88,29 +88,6 @@ RUNTIME_FUNCTION(Runtime_TransitionElementsKind) {
} }
// Push an object unto an array of objects if it is not already in the
// array. Returns true if the element was pushed on the stack and
// false otherwise.
RUNTIME_FUNCTION(Runtime_PushIfAbsent) {
HandleScope scope(isolate);
DCHECK(args.length() == 2);
CONVERT_ARG_HANDLE_CHECKED(JSArray, array, 0);
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, element, 1);
RUNTIME_ASSERT(array->HasFastSmiOrObjectElements());
int length = Smi::cast(array->length())->value();
FixedArray* elements = FixedArray::cast(array->elements());
for (int i = 0; i < length; i++) {
if (elements->get(i) == *element) return isolate->heap()->false_value();
}
// Strict not needed. Used for cycle detection in Array join implementation.
RETURN_FAILURE_ON_EXCEPTION(
isolate, JSObject::AddDataElement(array, length, element, NONE));
JSObject::ValidateElements(array);
return isolate->heap()->true_value();
}
// Moves all own elements of an object, that are below a limit, to positions // Moves all own elements of an object, that are below a limit, to positions
// starting at zero. All undefined values are placed after non-undefined values, // starting at zero. All undefined values are placed after non-undefined values,
// and are followed by non-existing element. Does not change the length // and are followed by non-existing element. Does not change the length
......
...@@ -642,13 +642,6 @@ RUNTIME_FUNCTION(Runtime_SparseJoinWithSeparator) { ...@@ -642,13 +642,6 @@ RUNTIME_FUNCTION(Runtime_SparseJoinWithSeparator) {
RUNTIME_ASSERT(elements_length <= elements_array->elements()->length()); RUNTIME_ASSERT(elements_length <= elements_array->elements()->length());
RUNTIME_ASSERT((elements_length & 1) == 0); // Even length. RUNTIME_ASSERT((elements_length & 1) == 0); // Even length.
FixedArray* elements = FixedArray::cast(elements_array->elements()); FixedArray* elements = FixedArray::cast(elements_array->elements());
for (int i = 0; i < elements_length; i += 2) {
RUNTIME_ASSERT(elements->get(i)->IsNumber());
CONVERT_NUMBER_CHECKED(uint32_t, position, Uint32, elements->get(i));
RUNTIME_ASSERT(position < array_length);
RUNTIME_ASSERT(elements->get(i + 1)->IsString());
}
{ {
DisallowHeapAllocation no_gc; DisallowHeapAllocation no_gc;
for (int i = 0; i < elements_length; i += 2) { for (int i = 0; i < elements_length; i += 2) {
......
...@@ -35,7 +35,6 @@ namespace internal { ...@@ -35,7 +35,6 @@ namespace internal {
F(FinishArrayPrototypeSetup, 1, 1) \ F(FinishArrayPrototypeSetup, 1, 1) \
F(SpecialArrayFunctions, 0, 1) \ F(SpecialArrayFunctions, 0, 1) \
F(TransitionElementsKind, 2, 1) \ F(TransitionElementsKind, 2, 1) \
F(PushIfAbsent, 2, 1) \
F(RemoveArrayHoles, 2, 1) \ F(RemoveArrayHoles, 2, 1) \
F(MoveArrayContents, 2, 1) \ F(MoveArrayContents, 2, 1) \
F(EstimateNumberOfElements, 1, 1) \ F(EstimateNumberOfElements, 1, 1) \
......
// Copyright 2016 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.
Object.defineProperty(Object.prototype, "length", {set() { throw "error" }});
Object.defineProperty(Object.prototype, "values", {set() { throw "error" }});
JSON.stringify({}, v=>v);
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