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}
This commit is contained in:
parent
421a67b0f4
commit
d358357478
230
src/js/array.js
230
src/js/array.js
@ -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) {
|
||||
var keys = new InternalArray();
|
||||
if (IS_NUMBER(indices)) {
|
||||
var keys = new InternalArray();
|
||||
// It's an interval
|
||||
var limit = indices;
|
||||
for (var i = 0; i < limit; ++i) {
|
||||
@ -92,61 +88,33 @@ function GetSortedArrayKeys(array, indices) {
|
||||
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) {
|
||||
var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
|
||||
var totalLength = 0;
|
||||
var elements = new InternalArray(keys.length * 2);
|
||||
var previousKey = -1;
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
function SparseJoinWithSeparatorJS(array, keys, length, convert, separator) {
|
||||
var keys_length = keys.length;
|
||||
var elements = new InternalArray(keys_length * 2);
|
||||
for (var i = 0; i < keys_length; i++) {
|
||||
var key = keys[i];
|
||||
if (key != previousKey) { // keys may contain duplicates.
|
||||
var e = array[key];
|
||||
if (!IS_STRING(e)) e = convert(e);
|
||||
elements[i * 2] = key;
|
||||
elements[i * 2 + 1] = e;
|
||||
previousKey = key;
|
||||
}
|
||||
var e = array[key];
|
||||
elements[i * 2] = key;
|
||||
elements[i * 2 + 1] = IS_STRING(e) ? e : convert(e);
|
||||
}
|
||||
return %SparseJoinWithSeparator(elements, len, separator);
|
||||
return %SparseJoinWithSeparator(elements, length, separator);
|
||||
}
|
||||
|
||||
|
||||
// Optimized for sparse arrays if separator is ''.
|
||||
function SparseJoin(array, len, convert) {
|
||||
var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
|
||||
var last_key = -1;
|
||||
function SparseJoin(array, keys, convert) {
|
||||
var keys_length = keys.length;
|
||||
|
||||
var elements = new InternalArray(keys_length);
|
||||
var elements_length = 0;
|
||||
|
||||
for (var i = 0; i < keys_length; i++) {
|
||||
var key = keys[i];
|
||||
if (key != last_key) {
|
||||
var e = array[key];
|
||||
if (!IS_STRING(e)) e = convert(e);
|
||||
elements[elements_length++] = e;
|
||||
last_key = key;
|
||||
}
|
||||
var e = array[keys[i]];
|
||||
elements[i] = IS_STRING(e) ? e : convert(e);
|
||||
}
|
||||
return %StringBuilderConcat(elements, elements_length, '');
|
||||
return %StringBuilderConcat(elements, keys_length, '');
|
||||
}
|
||||
|
||||
|
||||
@ -167,98 +135,122 @@ function UseSparseVariant(array, length, is_array, touched) {
|
||||
(touched > estimated_elements * 4);
|
||||
}
|
||||
|
||||
function Stack() {
|
||||
this.length = 0;
|
||||
this.values = new InternalArray();
|
||||
}
|
||||
|
||||
// Predeclare the instance variables on the prototype. Otherwise setting them in
|
||||
// the constructor will leak the instance through settings on Object.prototype.
|
||||
Stack.prototype.length = null;
|
||||
Stack.prototype.values = null;
|
||||
|
||||
function StackPush(stack, value) {
|
||||
stack.values[stack.length++] = value;
|
||||
}
|
||||
|
||||
function StackPop(stack) {
|
||||
stack.values[--stack.length] = null
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Global list of arrays visited during toString, toLocaleString and
|
||||
// join invocations.
|
||||
var visited_arrays = new Stack();
|
||||
|
||||
function DoJoin(array, length, is_array, separator, convert) {
|
||||
if (UseSparseVariant(array, length, is_array, length)) {
|
||||
%NormalizeElements(array);
|
||||
var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, length));
|
||||
if (separator === '') {
|
||||
if (keys.length === 0) return '';
|
||||
return SparseJoin(array, keys, convert);
|
||||
} else {
|
||||
return SparseJoinWithSeparatorJS(array, keys, length, convert, separator);
|
||||
}
|
||||
}
|
||||
|
||||
// Fast case for one-element arrays.
|
||||
if (length === 1) {
|
||||
var e = array[0];
|
||||
return IS_STRING(e) ? e : convert(e);
|
||||
}
|
||||
|
||||
// Construct an array for the elements.
|
||||
var elements = new InternalArray(length);
|
||||
|
||||
// We pull the empty separator check outside the loop for speed!
|
||||
if (separator === '') {
|
||||
for (var i = 0; i < length; i++) {
|
||||
var e = array[i];
|
||||
elements[i] = IS_STRING(e) ? e : convert(e);
|
||||
}
|
||||
return %StringBuilderConcat(elements, length, '');
|
||||
}
|
||||
// Non-empty separator case.
|
||||
// If the first element is a number then use the heuristic that the
|
||||
// remaining elements are also likely to be numbers.
|
||||
var e = array[0];
|
||||
if (IS_NUMBER(e)) {
|
||||
elements[0] = %_NumberToString(e);
|
||||
for (var i = 1; i < length; i++) {
|
||||
e = array[i];
|
||||
if (IS_NUMBER(e)) {
|
||||
elements[i] = %_NumberToString(e);
|
||||
} else {
|
||||
elements[i] = IS_STRING(e) ? e : convert(e);
|
||||
}
|
||||
}
|
||||
} 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 '';
|
||||
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 (!%PushIfAbsent(visited_arrays, array)) return '';
|
||||
if (StackHas(visited_arrays, array)) return '';
|
||||
StackPush(visited_arrays, array);
|
||||
}
|
||||
|
||||
// Attempt to convert the elements.
|
||||
try {
|
||||
if (UseSparseVariant(array, length, is_array, length)) {
|
||||
%NormalizeElements(array);
|
||||
if (separator.length == 0) {
|
||||
return SparseJoin(array, length, convert);
|
||||
} else {
|
||||
return SparseJoinWithSeparatorJS(array, length, convert, separator);
|
||||
}
|
||||
}
|
||||
|
||||
// Fast case for one-element arrays.
|
||||
if (length == 1) {
|
||||
var e = array[0];
|
||||
if (IS_STRING(e)) return e;
|
||||
return convert(e);
|
||||
}
|
||||
|
||||
// Construct an array for the elements.
|
||||
var elements = new InternalArray(length);
|
||||
|
||||
// We pull the empty separator check outside the loop for speed!
|
||||
if (separator.length == 0) {
|
||||
var elements_length = 0;
|
||||
for (var i = 0; i < length; i++) {
|
||||
var e = array[i];
|
||||
if (!IS_STRING(e)) e = convert(e);
|
||||
elements[elements_length++] = e;
|
||||
}
|
||||
elements.length = elements_length;
|
||||
return %StringBuilderConcat(elements, elements_length, '');
|
||||
}
|
||||
// Non-empty separator case.
|
||||
// If the first element is a number then use the heuristic that the
|
||||
// remaining elements are also likely to be numbers.
|
||||
var e = array[0];
|
||||
if (!IS_NUMBER(e)) {
|
||||
if (!IS_STRING(e)) e = convert(e);
|
||||
elements[0] = e;
|
||||
for (var i = 1; i < length; i++) {
|
||||
e = array[i];
|
||||
if (!IS_STRING(e)) e = convert(e);
|
||||
elements[i] = e;
|
||||
}
|
||||
} else {
|
||||
elements[0] = %_NumberToString(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);
|
||||
return DoJoin(array, length, is_array, separator, convert);
|
||||
} finally {
|
||||
// Make sure to remove the last element of the visited array no
|
||||
// matter what happens.
|
||||
if (is_array) visited_arrays.length = visited_arrays.length - 1;
|
||||
if (is_array) StackPop(visited_arrays);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ConvertToString(x) {
|
||||
if (IS_NULL_OR_UNDEFINED(x)) {
|
||||
return '';
|
||||
} else {
|
||||
return TO_STRING(x);
|
||||
}
|
||||
if (IS_NULL_OR_UNDEFINED(x)) return '';
|
||||
return TO_STRING(x);
|
||||
}
|
||||
|
||||
|
||||
function ConvertToLocaleString(e) {
|
||||
if (IS_NULL_OR_UNDEFINED(e)) {
|
||||
return '';
|
||||
} else {
|
||||
return TO_STRING(e.toLocaleString());
|
||||
}
|
||||
if (IS_NULL_OR_UNDEFINED(e)) return '';
|
||||
return TO_STRING(e.toLocaleString());
|
||||
}
|
||||
|
||||
|
||||
@ -1959,6 +1951,10 @@ utils.Export(function(to) {
|
||||
to.InnerArraySort = InnerArraySort;
|
||||
to.InnerArrayToLocaleString = InnerArrayToLocaleString;
|
||||
to.PackedArrayReverse = PackedArrayReverse;
|
||||
to.Stack = Stack;
|
||||
to.StackHas = StackHas;
|
||||
to.StackPush = StackPush;
|
||||
to.StackPop = StackPop;
|
||||
});
|
||||
|
||||
%InstallToContext([
|
||||
|
@ -19,6 +19,10 @@ var MakeTypeError;
|
||||
var MaxSimple;
|
||||
var MinSimple;
|
||||
var ObjectHasOwnProperty;
|
||||
var Stack;
|
||||
var StackHas;
|
||||
var StackPop;
|
||||
var StackPush;
|
||||
var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol");
|
||||
|
||||
utils.Import(function(from) {
|
||||
@ -26,6 +30,10 @@ utils.Import(function(from) {
|
||||
MaxSimple = from.MaxSimple;
|
||||
MinSimple = from.MinSimple;
|
||||
ObjectHasOwnProperty = from.ObjectHasOwnProperty;
|
||||
Stack = from.Stack;
|
||||
StackHas = from.StackHas;
|
||||
StackPop = from.StackPop;
|
||||
StackPush = from.StackPush;
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
@ -78,7 +86,8 @@ function JSONParse(text, reviver) {
|
||||
|
||||
|
||||
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;
|
||||
indent += gap;
|
||||
var partial = new InternalArray();
|
||||
@ -101,13 +110,14 @@ function SerializeArray(value, replacer, stack, indent, gap) {
|
||||
} else {
|
||||
final = "[]";
|
||||
}
|
||||
stack.pop();
|
||||
StackPop(stack);
|
||||
return final;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
indent += gap;
|
||||
var partial = new InternalArray();
|
||||
@ -146,7 +156,7 @@ function SerializeObject(value, replacer, stack, indent, gap) {
|
||||
} else {
|
||||
final = "{}";
|
||||
}
|
||||
stack.pop();
|
||||
StackPop(stack);
|
||||
return final;
|
||||
}
|
||||
|
||||
@ -241,7 +251,7 @@ function JSONStringify(value, replacer, space) {
|
||||
if (!IS_CALLABLE(replacer) && !property_list && !gap && !IS_PROXY(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) {
|
||||
var holder = {};
|
||||
holder[key] = object;
|
||||
// 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]);
|
||||
|
@ -17482,9 +17482,6 @@ template Handle<NameDictionary>
|
||||
Dictionary<NameDictionary, NameDictionaryShape, Handle<Name> >::
|
||||
EnsureCapacity(Handle<NameDictionary>, int, Handle<Name>);
|
||||
|
||||
template bool Dictionary<SeededNumberDictionary, SeededNumberDictionaryShape,
|
||||
uint32_t>::HasComplexElements();
|
||||
|
||||
template int HashTable<SeededNumberDictionary, SeededNumberDictionaryShape,
|
||||
uint32_t>::FindEntry(uint32_t);
|
||||
|
||||
@ -18325,6 +18322,21 @@ void Dictionary<Derived, Shape, Key>::AddEntry(
|
||||
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,
|
||||
bool used_as_prototype) {
|
||||
@ -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>
|
||||
struct EnumIndexComparator {
|
||||
explicit EnumIndexComparator(Dictionary* dict) : dict(dict) {}
|
||||
|
@ -3485,10 +3485,6 @@ class Dictionary: public HashTable<Derived, Shape, Key> {
|
||||
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 };
|
||||
|
||||
// Fill in details for properties into storage.
|
||||
@ -3721,6 +3717,10 @@ class SeededNumberDictionary
|
||||
|
||||
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
|
||||
// for the elements kept in this dictionary. We require slow
|
||||
// elements if an element has been added at an index larger than
|
||||
|
@ -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
|
||||
// starting at zero. All undefined values are placed after non-undefined values,
|
||||
// and are followed by non-existing element. Does not change the length
|
||||
|
@ -642,13 +642,6 @@ RUNTIME_FUNCTION(Runtime_SparseJoinWithSeparator) {
|
||||
RUNTIME_ASSERT(elements_length <= elements_array->elements()->length());
|
||||
RUNTIME_ASSERT((elements_length & 1) == 0); // Even length.
|
||||
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;
|
||||
for (int i = 0; i < elements_length; i += 2) {
|
||||
|
@ -35,7 +35,6 @@ namespace internal {
|
||||
F(FinishArrayPrototypeSetup, 1, 1) \
|
||||
F(SpecialArrayFunctions, 0, 1) \
|
||||
F(TransitionElementsKind, 2, 1) \
|
||||
F(PushIfAbsent, 2, 1) \
|
||||
F(RemoveArrayHoles, 2, 1) \
|
||||
F(MoveArrayContents, 2, 1) \
|
||||
F(EstimateNumberOfElements, 1, 1) \
|
||||
|
8
test/mjsunit/json-stringify-stack.js
Normal file
8
test/mjsunit/json-stringify-stack.js
Normal file
@ -0,0 +1,8 @@
|
||||
// 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);
|
Loading…
Reference in New Issue
Block a user