Commit f470cf27 authored by lrn@chromium.org's avatar lrn@chromium.org

Handle join of sparse arrays with non-empty separator more efficiently.

BUG=v8:1028

Review URL: http://codereview.chromium.org/6902144

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@7716 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 00bc2db2
...@@ -67,6 +67,25 @@ function GetSortedArrayKeys(array, intervals) { ...@@ -67,6 +67,25 @@ function GetSortedArrayKeys(array, intervals) {
} }
function SparseJoinWithSeparator(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++) {
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;
}
}
return %SparseJoinWithSeparator(elements, len, separator);
}
// Optimized for sparse arrays if separator is ''. // Optimized for sparse arrays if separator is ''.
function SparseJoin(array, len, convert) { function SparseJoin(array, len, convert) {
var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len)); var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
...@@ -110,8 +129,12 @@ function Join(array, length, separator, convert) { ...@@ -110,8 +129,12 @@ function Join(array, length, separator, convert) {
// Attempt to convert the elements. // Attempt to convert the elements.
try { try {
if (UseSparseVariant(array, length, is_array) && (separator.length == 0)) { if (UseSparseVariant(array, length, is_array)) {
return SparseJoin(array, length, convert); if (separator.length == 0) {
return SparseJoin(array, length, convert);
} else {
return SparseJoinWithSeparator(array, length, convert, separator);
}
} }
// Fast case for one-element arrays. // Fast case for one-element arrays.
...@@ -129,10 +152,8 @@ function Join(array, length, separator, convert) { ...@@ -129,10 +152,8 @@ function Join(array, length, separator, convert) {
var elements_length = 0; var elements_length = 0;
for (var i = 0; i < length; i++) { for (var i = 0; i < length; i++) {
var e = array[i]; var e = array[i];
if (!IS_UNDEFINED(e)) { if (!IS_STRING(e)) e = convert(e);
if (!IS_STRING(e)) e = convert(e); elements[elements_length++] = e;
elements[elements_length++] = e;
}
} }
elements.length = elements_length; elements.length = elements_length;
var result = %_FastAsciiArrayJoin(elements, ''); var result = %_FastAsciiArrayJoin(elements, '');
...@@ -151,11 +172,12 @@ function Join(array, length, separator, convert) { ...@@ -151,11 +172,12 @@ function Join(array, length, separator, convert) {
} else { } else {
for (var i = 0; i < length; i++) { for (var i = 0; i < length; i++) {
var e = array[i]; var e = array[i];
if (IS_NUMBER(e)) elements[i] = %_NumberToString(e); if (IS_NUMBER(e)) {
else { e = %_NumberToString(e);
if (!IS_STRING(e)) e = convert(e); } else if (!IS_STRING(e)) {
e = convert(e);
}
elements[i] = e; elements[i] = e;
}
} }
} }
var result = %_FastAsciiArrayJoin(elements, separator); var result = %_FastAsciiArrayJoin(elements, separator);
......
...@@ -6159,6 +6159,135 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_StringBuilderJoin) { ...@@ -6159,6 +6159,135 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_StringBuilderJoin) {
return answer; return answer;
} }
template <typename Char>
static void JoinSparseArrayWithSeparator(FixedArray* elements,
int elements_length,
uint32_t array_length,
String* separator,
Vector<Char> buffer) {
int previous_separator_position = 0;
int separator_length = separator->length();
int cursor = 0;
for (int i = 0; i < elements_length; i += 2) {
int position = NumberToInt32(elements->get(i));
String* string = String::cast(elements->get(i + 1));
int string_length = string->length();
if (string->length() > 0) {
while (previous_separator_position < position) {
String::WriteToFlat<Char>(separator, &buffer[cursor],
0, separator_length);
cursor += separator_length;
previous_separator_position++;
}
String::WriteToFlat<Char>(string, &buffer[cursor],
0, string_length);
cursor += string->length();
}
}
if (separator_length > 0) {
// Array length must be representable as a signed 32-bit number,
// otherwise the total string length would have been too large.
ASSERT(array_length <= 0x7fffffff); // Is int32_t.
int last_array_index = static_cast<int>(array_length - 1);
while (previous_separator_position < last_array_index) {
String::WriteToFlat<Char>(separator, &buffer[cursor],
0, separator_length);
cursor += separator_length;
previous_separator_position++;
}
}
ASSERT(cursor <= buffer.length());
}
RUNTIME_FUNCTION(MaybeObject*, Runtime_SparseJoinWithSeparator) {
NoHandleAllocation ha;
ASSERT(args.length() == 3);
CONVERT_CHECKED(JSArray, elements_array, args[0]);
RUNTIME_ASSERT(elements_array->HasFastElements());
CONVERT_NUMBER_CHECKED(uint32_t, array_length, Uint32, args[1]);
CONVERT_CHECKED(String, separator, args[2]);
// elements_array is fast-mode JSarray of alternating positions
// (increasing order) and strings.
// array_length is length of original array (used to add separators);
// separator is string to put between elements. Assumed to be non-empty.
// Find total length of join result.
int string_length = 0;
bool is_ascii = true;
int max_string_length = SeqAsciiString::kMaxLength;
bool overflow = false;
CONVERT_NUMBER_CHECKED(int, elements_length,
Int32, elements_array->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_CHECKED(String, string, elements->get(i + 1));
int length = string->length();
if (is_ascii && !string->IsAsciiRepresentation()) {
is_ascii = false;
max_string_length = SeqTwoByteString::kMaxLength;
}
if (length > max_string_length ||
max_string_length - length < string_length) {
overflow = true;
break;
}
string_length += length;
}
int separator_length = separator->length();
if (!overflow && separator_length > 0) {
if (array_length <= 0x7fffffffu) {
int separator_count = static_cast<int>(array_length) - 1;
int remaining_length = max_string_length - string_length;
if ((remaining_length / separator_length) >= separator_count) {
string_length += separator_length * (array_length - 1);
} else {
// Not room for the separators within the maximal string length.
overflow = true;
}
} else {
// Nonempty separator and at least 2^31-1 separators necessary
// means that the string is too large to create.
STATIC_ASSERT(String::kMaxLength < 0x7fffffff);
overflow = true;
}
}
if (overflow) {
// Throw OutOfMemory exception for creating too large a string.
V8::FatalProcessOutOfMemory("Array join result too large.");
}
if (is_ascii) {
MaybeObject* result_allocation =
isolate->heap()->AllocateRawAsciiString(string_length);
if (result_allocation->IsFailure()) return result_allocation;
SeqAsciiString* result_string =
SeqAsciiString::cast(result_allocation->ToObjectUnchecked());
JoinSparseArrayWithSeparator<char>(elements,
elements_length,
array_length,
separator,
Vector<char>(result_string->GetChars(),
string_length));
return result_string;
} else {
MaybeObject* result_allocation =
isolate->heap()->AllocateRawTwoByteString(string_length);
if (result_allocation->IsFailure()) return result_allocation;
SeqTwoByteString* result_string =
SeqTwoByteString::cast(result_allocation->ToObjectUnchecked());
JoinSparseArrayWithSeparator<uc16>(elements,
elements_length,
array_length,
separator,
Vector<uc16>(result_string->GetChars(),
string_length));
return result_string;
}
}
RUNTIME_FUNCTION(MaybeObject*, Runtime_NumberOr) { RUNTIME_FUNCTION(MaybeObject*, Runtime_NumberOr) {
NoHandleAllocation ha; NoHandleAllocation ha;
......
...@@ -132,6 +132,7 @@ namespace internal { ...@@ -132,6 +132,7 @@ namespace internal {
F(StringAdd, 2, 1) \ F(StringAdd, 2, 1) \
F(StringBuilderConcat, 3, 1) \ F(StringBuilderConcat, 3, 1) \
F(StringBuilderJoin, 3, 1) \ F(StringBuilderJoin, 3, 1) \
F(SparseJoinWithSeparator, 3, 1) \
\ \
/* Bit operations */ \ /* Bit operations */ \
F(NumberOr, 2, 1) \ F(NumberOr, 2, 1) \
......
...@@ -44,7 +44,8 @@ assertEquals('1,2********3********4********5,6********', a.join('********')); ...@@ -44,7 +44,8 @@ assertEquals('1,2********3********4********5,6********', a.join('********'));
assertEquals('1,2**********3**********4**********5,6**********', a.join('**********')); assertEquals('1,2**********3**********4**********5,6**********', a.join('**********'));
// Replace array.prototype.toString. // Replace array.prototype.toString.
Array.prototype.toString = function() { return "array"; } var oldToString = Array.prototype.toString;
Array.prototype.toString = function() { return "array"; };
assertEquals('array34arrayarray', a.join('')); assertEquals('array34arrayarray', a.join(''));
assertEquals('array*3*4*array*array', a.join('*')); assertEquals('array*3*4*array*array', a.join('*'));
assertEquals('array**3**4**array**array', a.join('**')); assertEquals('array**3**4**array**array', a.join('**'));
...@@ -52,7 +53,7 @@ assertEquals('array****3****4****array****array', a.join('****')); ...@@ -52,7 +53,7 @@ assertEquals('array****3****4****array****array', a.join('****'));
assertEquals('array********3********4********array********array', a.join('********')); assertEquals('array********3********4********array********array', a.join('********'));
assertEquals('array**********3**********4**********array**********array', a.join('**********')); assertEquals('array**********3**********4**********array**********array', a.join('**********'));
Array.prototype.toString = function() { throw 42; } Array.prototype.toString = function() { throw 42; };
assertThrows("a.join('')"); assertThrows("a.join('')");
assertThrows("a.join('*')"); assertThrows("a.join('*')");
assertThrows("a.join('**')"); assertThrows("a.join('**')");
...@@ -60,7 +61,7 @@ assertThrows("a.join('****')"); ...@@ -60,7 +61,7 @@ assertThrows("a.join('****')");
assertThrows("a.join('********')"); assertThrows("a.join('********')");
assertThrows("a.join('**********')"); assertThrows("a.join('**********')");
Array.prototype.toString = function() { return "array"; } Array.prototype.toString = function() { return "array"; };
assertEquals('array34arrayarray', a.join('')); assertEquals('array34arrayarray', a.join(''));
assertEquals('array*3*4*array*array', a.join('*')); assertEquals('array*3*4*array*array', a.join('*'));
assertEquals('array**3**4**array**array', a.join('**')); assertEquals('array**3**4**array**array', a.join('**'));
...@@ -68,3 +69,25 @@ assertEquals('array****3****4****array****array', a.join('****')); ...@@ -68,3 +69,25 @@ assertEquals('array****3****4****array****array', a.join('****'));
assertEquals('array********3********4********array********array', a.join('********')); assertEquals('array********3********4********array********array', a.join('********'));
assertEquals('array**********3**********4**********array**********array', a.join('**********')); assertEquals('array**********3**********4**********array**********array', a.join('**********'));
// Restore original toString.
delete Array.prototype.toString;
if (Array.prototype.toString != oldToString) {
Array.prototype.toString = oldToString;
}
var a = new Array(123123123);
assertEquals(123123122, String(a).length);
assertEquals(123123122, a.join(",").length);
assertEquals(246246244, a.join("oo").length);
a = new Array(Math.pow(2,32) - 1); // Max length.
assertEquals("", a.join(""));
a[123123123] = "o";
a[1255215215] = "p";
assertEquals("op", a.join(""));
a = new Array(100001);
for (var i = 0; i < a.length; i++) a[i] = undefined;
a[5] = "ab";
a[90000] = "cd";
assertEquals("abcd", a.join("")); // Must not throw.
\ No newline at end of file
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