Commit 952c0976 authored by peterwmwong's avatar peterwmwong Committed by Commit Bot

[builtins] Port Array.p.join to Torque.

This also includes ports of Array.p.toString and Array.p.toLocaleString.
Many parts of the old JS implementation are preserved, because
TypedArray.p.join still relies on it.  These will be removed once
TypedArray.p.join is ported to Torque.

To simplify implementation, special handling of extremely sparse arrays
has been removed.

Performance improvements vary by array size, elements, and sparse-ness.
Some quick numbers and graphs are here:
https://docs.google.com/spreadsheets/d/125VLmRMudk8XaomLCsZQ1ewc94WCqht-8GQwU3s9BW8/edit#gid=2087673710

Cq-Include-Trybots: luci.chromium.try:linux_chromium_headless_rel;luci.v8.try:v8_linux_noi18n_rel_ng;master.tryserver.blink:linux_trusty_blink_rel
Change-Id: Ia4069a068403ce36676c37401d349aefc976b045
Reviewed-on: https://chromium-review.googlesource.com/c/1196693
Commit-Queue: Peter Wong <peter.wm.wong@gmail.com>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarTobias Tebbi <tebbi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#56699}
parent f64edae0
......@@ -908,6 +908,7 @@ torque_files = [
"src/builtins/array.tq",
"src/builtins/array-copywithin.tq",
"src/builtins/array-foreach.tq",
"src/builtins/array-join.tq",
"src/builtins/array-lastindexof.tq",
"src/builtins/array-reverse.tq",
"src/builtins/array-splice.tq",
......
......@@ -1748,6 +1748,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
1, false);
SimpleInstallFunction(isolate_, proto, "indexOf", Builtins::kArrayIndexOf,
1, false);
SimpleInstallFunction(isolate_, proto, "join",
Builtins::kArrayPrototypeJoin, 1, false);
SimpleInstallFunction(isolate_, proto, "keys",
Builtins::kArrayPrototypeKeys, 0, true,
BuiltinFunctionId::kArrayKeys);
......@@ -1771,6 +1773,10 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
false);
SimpleInstallFunction(isolate_, proto, "reduceRight",
Builtins::kArrayReduceRight, 1, false);
SimpleInstallFunction(isolate_, proto, "toLocaleString",
Builtins::kArrayPrototypeToLocaleString, 0, false);
SimpleInstallFunction(isolate_, proto, "toString",
Builtins::kArrayPrototypeToString, 0, false);
}
{ // --- A r r a y I t e r a t o r ---
......
This diff is collapsed.
This diff is collapsed.
......@@ -72,6 +72,66 @@ class ArrayBuiltinsAssembler : public BaseBuiltinsFromDSLAssembler {
void FillFixedArrayWithSmiZero(TNode<FixedArray> array,
TNode<Smi> smi_length);
TNode<String> CallJSArrayArrayJoinConcatToSequentialString(
TNode<FixedArray> fixed_array, TNode<IntPtrT> length, TNode<String> sep,
TNode<String> dest) {
TNode<ExternalReference> func = ExternalConstant(
ExternalReference::jsarray_array_join_concat_to_sequential_string());
TNode<ExternalReference> isolate_ptr =
ExternalConstant(ExternalReference::isolate_address(isolate()));
return UncheckedCast<String>(
CallCFunction5(MachineType::AnyTagged(), // <return> String*
MachineType::Pointer(), // Isolate*
MachineType::AnyTagged(), // FixedArray* fixed_array
MachineType::IntPtr(), // intptr_t length
MachineType::AnyTagged(), // String* sep
MachineType::AnyTagged(), // String* dest
func, isolate_ptr, fixed_array, length, sep, dest));
}
// Temporary Torque support for Array.prototype.join().
// TODO(pwong): Remove this when Torque supports exception handlers.
TNode<Object> CallLoadJoinElement(TNode<Context> context,
TNode<Code> loadJoinElement,
TNode<JSReceiver> receiver, TNode<Number> k,
Label* if_exception,
TVariable<Object>* var_exception) {
// Calling a specialization of LoadJoinElement (see array-join.tq), requires
// a descriptor. We arbitrarily use one of specialization's descriptor, as
// all specializations share the same interface.
TNode<Object> result = CallStub(
Builtins::CallableFor(isolate(),
Builtins::kLoadJoinElement20ATDictionaryElements)
.descriptor(),
loadJoinElement, context, receiver, k);
GotoIfException(result, if_exception, var_exception);
return result;
}
// Temporary Torque support for Array.prototype.join().
// TODO(pwong): Remove this when Torque supports exception handlers.
TNode<String> CallConvertToLocaleString(TNode<Context> context,
TNode<Object> element,
TNode<Object> locales,
TNode<Object> options,
Label* if_exception,
TVariable<Object>* var_exception) {
TNode<Object> result = CallBuiltin(Builtins::kConvertToLocaleString,
context, element, locales, options);
GotoIfException(result, if_exception, var_exception);
return CAST(result);
}
// Temporary Torque support for Array.prototype.join().
// TODO(pwong): Remove this when Torque supports exception handlers.
TNode<String> CallToString(TNode<Context> context, TNode<Object> obj,
Label* if_exception,
TVariable<Object>* var_exception) {
TNode<Object> result = CallBuiltin(Builtins::kToString, context, obj);
GotoIfException(result, if_exception, var_exception);
return CAST(result);
}
protected:
TNode<Context> context() { return context_; }
TNode<Object> receiver() { return receiver_; }
......
......@@ -840,6 +840,7 @@ namespace internal {
/* ES #sec-object.prototype.tolocalestring */ \
TFJ(ObjectPrototypeToLocaleString, 0, kReceiver) \
CPP(ObjectSeal) \
TFS(ObjectToString, kReceiver) \
TFJ(ObjectValues, 1, kReceiver, kObject) \
\
/* instanceof */ \
......
......@@ -814,7 +814,13 @@ TF_BUILTIN(ObjectPrototypeIsPrototypeOf, ObjectBuiltinsAssembler) {
}
// ES #sec-object.prototype.tostring
TF_BUILTIN(ObjectPrototypeToString, ObjectBuiltinsAssembler) {
TF_BUILTIN(ObjectPrototypeToString, CodeStubAssembler) {
TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
Return(CallBuiltin(Builtins::kObjectToString, context, receiver));
}
TF_BUILTIN(ObjectToString, ObjectBuiltinsAssembler) {
Label checkstringtag(this), if_apiobject(this, Label::kDeferred),
if_arguments(this), if_array(this), if_boolean(this), if_date(this),
if_error(this), if_function(this), if_number(this, Label::kDeferred),
......
......@@ -618,15 +618,20 @@ TNode<Smi> CodeStubAssembler::SmiMin(TNode<Smi> a, TNode<Smi> b) {
return SelectConstant<Smi>(SmiLessThan(a, b), a, b);
}
TNode<IntPtrT> CodeStubAssembler::TryIntPtrAdd(TNode<IntPtrT> a,
TNode<IntPtrT> b,
Label* if_overflow) {
TNode<PairT<IntPtrT, BoolT>> pair = IntPtrAddWithOverflow(a, b);
TNode<BoolT> overflow = Projection<1>(pair);
GotoIf(overflow, if_overflow);
return Projection<0>(pair);
}
TNode<Smi> CodeStubAssembler::TrySmiAdd(TNode<Smi> lhs, TNode<Smi> rhs,
Label* if_overflow) {
if (SmiValuesAre32Bits()) {
TNode<PairT<IntPtrT, BoolT>> pair = IntPtrAddWithOverflow(
BitcastTaggedToWord(lhs), BitcastTaggedToWord(rhs));
TNode<BoolT> overflow = Projection<1>(pair);
GotoIf(overflow, if_overflow);
TNode<IntPtrT> result = Projection<0>(pair);
return BitcastWordToTaggedSigned(result);
return BitcastWordToTaggedSigned(TryIntPtrAdd(
BitcastTaggedToWord(lhs), BitcastTaggedToWord(rhs), if_overflow));
} else {
DCHECK(SmiValuesAre31Bits());
TNode<PairT<Int32T, BoolT>> pair =
......@@ -967,6 +972,12 @@ TNode<Float64T> CodeStubAssembler::LoadDoubleWithHoleCheck(
SMI_PARAMETERS, if_hole);
}
TNode<Float64T> CodeStubAssembler::LoadDoubleWithHoleCheck(
TNode<FixedDoubleArray> array, TNode<IntPtrT> index, Label* if_hole) {
return LoadFixedDoubleArrayElement(array, index, MachineType::Float64(), 0,
INTPTR_PARAMETERS, if_hole);
}
void CodeStubAssembler::BranchIfPrototypesHaveNoElements(
Node* receiver_map, Label* definitely_no_elements,
Label* possibly_elements) {
......@@ -5882,6 +5893,12 @@ TNode<BoolT> CodeStubAssembler::IsOneByteStringInstanceType(
Int32Constant(kOneByteStringTag));
}
TNode<BoolT> CodeStubAssembler::HasOnlyOneByteChars(
TNode<Int32T> instance_type) {
CSA_ASSERT(this, IsStringInstanceType(instance_type));
return IsSetWord32(instance_type, kStringEncodingMask | kOneByteDataHintMask);
}
TNode<BoolT> CodeStubAssembler::IsSequentialStringInstanceType(
SloppyTNode<Int32T> instance_type) {
CSA_ASSERT(this, IsStringInstanceType(instance_type));
......@@ -12849,6 +12866,11 @@ Node* CodeStubAssembler::IsElementsKindGreaterThan(
return Int32GreaterThan(target_kind, Int32Constant(reference_kind));
}
TNode<BoolT> CodeStubAssembler::IsElementsKindLessThanOrEqual(
TNode<Int32T> target_kind, ElementsKind reference_kind) {
return Int32LessThanOrEqual(target_kind, Int32Constant(reference_kind));
}
Node* CodeStubAssembler::IsDebugActive() {
Node* is_debug_active = Load(
MachineType::Uint8(),
......
......@@ -336,6 +336,11 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
return CAST(heap_object);
}
TNode<String> HeapObjectToString(TNode<HeapObject> heap_object, Label* fail) {
GotoIfNot(IsString(heap_object), fail);
return CAST(heap_object);
}
TNode<HeapNumber> UnsafeCastNumberToHeapNumber(TNode<Number> p_n) {
return CAST(p_n);
}
......@@ -409,6 +414,10 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
TNode<Map> UnsafeCastObjectToMap(TNode<Object> p_o) { return CAST(p_o); }
TNode<String> UnsafeCastObjectToString(TNode<Object> p_o) {
return CAST(p_o);
}
TNode<JSArgumentsObjectWithLength> RawCastObjectToJSArgumentsObjectWithLength(
TNode<Object> p_o) {
return TNode<JSArgumentsObjectWithLength>::UncheckedCast(p_o);
......@@ -535,6 +544,8 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
#undef SMI_ARITHMETIC_BINOP
TNode<Smi> SmiInc(TNode<Smi> value) { return SmiAdd(value, SmiConstant(1)); }
TNode<IntPtrT> TryIntPtrAdd(TNode<IntPtrT> a, TNode<IntPtrT> b,
Label* if_overflow);
TNode<Smi> TrySmiAdd(TNode<Smi> a, TNode<Smi> b, Label* if_overflow);
TNode<Smi> TrySmiSub(TNode<Smi> a, TNode<Smi> b, Label* if_overflow);
......@@ -1107,6 +1118,9 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
TNode<Float64T> LoadDoubleWithHoleCheck(TNode<FixedDoubleArray> array,
TNode<Smi> index,
Label* if_hole = nullptr);
TNode<Float64T> LoadDoubleWithHoleCheck(TNode<FixedDoubleArray> array,
TNode<IntPtrT> index,
Label* if_hole = nullptr);
// Load Float64 value by |base| + |offset| address. If the value is a double
// hole then jump to |if_hole|. If |machine_type| is None then only the hole
......@@ -1206,6 +1220,11 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
return StoreFixedArrayElement(object, IntPtrConstant(index), value,
barrier_mode);
}
void StoreFixedArrayElement(TNode<FixedArray> object, int index,
TNode<Smi> value) {
return StoreFixedArrayElement(object, IntPtrConstant(index), value,
SKIP_WRITE_BARRIER);
}
Node* StoreJSArrayLength(TNode<JSArray> array, TNode<Smi> length);
Node* StoreElements(TNode<Object> object, TNode<FixedArrayBase> elements);
......@@ -1241,6 +1260,10 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
StoreFixedArrayElement(array, index, value, barrier_mode, 0,
SMI_PARAMETERS);
}
void StoreFixedArrayElement(TNode<FixedArray> array, TNode<IntPtrT> index,
TNode<Smi> value) {
StoreFixedArrayElement(array, index, value, SKIP_WRITE_BARRIER, 0);
}
void StoreFixedDoubleArrayElement(
TNode<FixedDoubleArray> object, Node* index, TNode<Float64T> value,
......@@ -1492,6 +1515,15 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
return result;
}
TNode<FixedArray> AllocateFixedArrayWithHoles(TNode<IntPtrT> capacity,
AllocationFlags flags) {
TNode<FixedArray> result = UncheckedCast<FixedArray>(
AllocateFixedArray(PACKED_ELEMENTS, capacity, flags));
FillFixedArrayWithValue(PACKED_ELEMENTS, result, IntPtrConstant(0),
capacity, RootIndex::kTheHoleValue);
return result;
}
Node* AllocatePropertyArray(Node* capacity,
ParameterMode mode = INTPTR_PARAMETERS,
AllocationFlags flags = kNone);
......@@ -1966,6 +1998,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
TNode<BoolT> IsNullOrUndefined(SloppyTNode<Object> object);
TNode<BoolT> IsNumberDictionary(SloppyTNode<HeapObject> object);
TNode<BoolT> IsOneByteStringInstanceType(SloppyTNode<Int32T> instance_type);
TNode<BoolT> HasOnlyOneByteChars(TNode<Int32T> instance_type);
TNode<BoolT> IsPrimitiveInstanceType(SloppyTNode<Int32T> instance_type);
TNode<BoolT> IsPrivateSymbol(SloppyTNode<HeapObject> object);
TNode<BoolT> IsPromiseCapability(SloppyTNode<HeapObject> object);
......@@ -2058,6 +2091,8 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
Node* IsHoleyFastElementsKind(Node* elements_kind);
Node* IsElementsKindGreaterThan(Node* target_kind,
ElementsKind reference_kind);
TNode<BoolT> IsElementsKindLessThanOrEqual(TNode<Int32T> target_kind,
ElementsKind reference_kind);
// String helpers.
// Load a character from a String (might flatten a ConsString).
......@@ -2398,6 +2433,11 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
LoadFixedArrayElement(dictionary, Dictionary::kNumberOfElementsIndex));
}
TNode<Smi> GetNumberDictionaryNumberOfElements(
TNode<NumberDictionary> dictionary) {
return GetNumberOfElements<NumberDictionary>(dictionary);
}
template <class Dictionary>
void SetNumberOfElements(TNode<Dictionary> dictionary,
TNode<Smi> num_elements_smi) {
......@@ -2833,6 +2873,11 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
if_false);
}
void BranchIfNumberNotEqual(TNode<Number> left, TNode<Number> right,
Label* if_true, Label* if_false) {
BranchIfNumberEqual(left, right, if_false, if_true);
}
void BranchIfNumberLessThan(TNode<Number> left, TNode<Number> right,
Label* if_true, Label* if_false) {
BranchIfNumberRelationalComparison(Operation::kLessThan, left, right,
......
......@@ -115,6 +115,7 @@ enum ContextLookupFlags {
V(ARRAY_BUFFER_MAP_INDEX, Map, array_buffer_map) \
V(ARRAY_BUFFER_NOINIT_FUN_INDEX, JSFunction, array_buffer_noinit_fun) \
V(ARRAY_FUNCTION_INDEX, JSFunction, array_function) \
V(ARRAY_JOIN_STACK_INDEX, HeapObject, array_join_stack) \
V(ASYNC_FROM_SYNC_ITERATOR_MAP_INDEX, Map, async_from_sync_iterator_map) \
V(ASYNC_FUNCTION_AWAIT_REJECT_SHARED_FUN, SharedFunctionInfo, \
async_function_await_reject_shared_fun) \
......
......@@ -552,10 +552,13 @@ DebugInfo::SideEffectState BuiltinGetSideEffectState(Builtins::Name id) {
case Builtins::kArrayPrototypeFindIndex:
case Builtins::kArrayPrototypeFlat:
case Builtins::kArrayPrototypeFlatMap:
case Builtins::kArrayPrototypeJoin:
case Builtins::kArrayPrototypeKeys:
case Builtins::kArrayPrototypeLastIndexOf:
case Builtins::kArrayPrototypeSlice:
case Builtins::kArrayPrototypeSort:
case Builtins::kArrayPrototypeToLocaleString:
case Builtins::kArrayPrototypeToString:
case Builtins::kArrayForEach:
case Builtins::kArrayEvery:
case Builtins::kArraySome:
......
......@@ -735,6 +735,12 @@ ExternalReference ExternalReference::search_string_raw() {
return ExternalReference(Redirect(FUNCTION_ADDR(f)));
}
ExternalReference
ExternalReference::jsarray_array_join_concat_to_sequential_string() {
return ExternalReference(
Redirect(FUNCTION_ADDR(JSArray::ArrayJoinConcatToSequentialString)));
}
ExternalReference ExternalReference::search_string_raw_one_one() {
return search_string_raw<const uint8_t, const uint8_t>();
}
......
......@@ -101,18 +101,18 @@ class StatsCounter;
V(ieee754_acosh_function, "base::ieee754::acosh") \
V(ieee754_asin_function, "base::ieee754::asin") \
V(ieee754_asinh_function, "base::ieee754::asinh") \
V(ieee754_atan2_function, "base::ieee754::atan2") \
V(ieee754_atan_function, "base::ieee754::atan") \
V(ieee754_atan2_function, "base::ieee754::atan2") \
V(ieee754_atanh_function, "base::ieee754::atanh") \
V(ieee754_cbrt_function, "base::ieee754::cbrt") \
V(ieee754_cos_function, "base::ieee754::cos") \
V(ieee754_cosh_function, "base::ieee754::cosh") \
V(ieee754_exp_function, "base::ieee754::exp") \
V(ieee754_expm1_function, "base::ieee754::expm1") \
V(ieee754_log_function, "base::ieee754::log") \
V(ieee754_log10_function, "base::ieee754::log10") \
V(ieee754_log1p_function, "base::ieee754::log1p") \
V(ieee754_log2_function, "base::ieee754::log2") \
V(ieee754_log_function, "base::ieee754::log") \
V(ieee754_sin_function, "base::ieee754::sin") \
V(ieee754_sinh_function, "base::ieee754::sinh") \
V(ieee754_tan_function, "base::ieee754::tan") \
......@@ -123,6 +123,8 @@ class StatsCounter;
"JSObject::InvalidatePrototypeChains()") \
V(invoke_accessor_getter_callback, "InvokeAccessorGetterCallback") \
V(invoke_function_callback, "InvokeFunctionCallback") \
V(jsarray_array_join_concat_to_sequential_string, \
"jsarray_array_join_concat_to_sequential_string") \
V(jsreceiver_create_identity_hash, "jsreceiver_create_identity_hash") \
V(libc_memchr_function, "libc_memchr") \
V(libc_memcpy_function, "libc_memcpy") \
......@@ -136,13 +138,13 @@ class StatsCounter;
V(power_double_double_function, "power_double_double_function") \
V(printf_function, "printf") \
V(refill_math_random, "MathRandom::RefillCache") \
V(store_buffer_overflow_function, "StoreBuffer::StoreBufferOverflow") \
V(search_string_raw_one_one, "search_string_raw_one_one") \
V(search_string_raw_one_two, "search_string_raw_one_two") \
V(search_string_raw_two_one, "search_string_raw_two_one") \
V(search_string_raw_two_two, "search_string_raw_two_two") \
V(try_internalize_string_function, "try_internalize_string_function") \
V(smi_lexicographic_compare_function, "smi_lexicographic_compare_function") \
V(store_buffer_overflow_function, "StoreBuffer::StoreBufferOverflow") \
V(try_internalize_string_function, "try_internalize_string_function") \
V(wasm_call_trap_callback_for_testing, \
"wasm::call_trap_callback_for_testing") \
V(wasm_f32_ceil, "wasm::f32_ceil_wrapper") \
......
......@@ -29,11 +29,12 @@ function ArraySpeciesCreate(array, length) {
return new constructor(length);
}
// TODO(pwong): Remove once TypedArray.prototype.join() is ported to Torque.
function KeySortCompare(a, b) {
return a - b;
}
// TODO(pwong): Remove once TypedArray.prototype.join() is ported to Torque.
function GetSortedArrayKeys(array, indices) {
if (IS_NUMBER(indices)) {
// It's an interval
......@@ -50,7 +51,7 @@ function GetSortedArrayKeys(array, indices) {
return InnerArraySort(indices, indices.length, KeySortCompare);
}
// TODO(pwong): Remove once TypedArray.prototype.join() is ported to Torque.
function SparseJoinWithSeparatorJS(
array, keys, length, use_locale, separator, locales, options) {
var keys_length = keys.length;
......@@ -64,7 +65,7 @@ function SparseJoinWithSeparatorJS(
return %SparseJoinWithSeparator(elements, length, separator);
}
// TODO(pwong): Remove once TypedArray.prototype.join() is ported to Torque.
// Optimized for sparse arrays if separator is ''.
function SparseJoin(array, keys, use_locale, locales, options) {
var keys_length = keys.length;
......@@ -75,7 +76,7 @@ function SparseJoin(array, keys, use_locale, locales, options) {
return %StringBuilderConcat(elements, keys_length, '');
}
// TODO(pwong): Remove once TypedArray.prototype.join() is ported to Torque.
function UseSparseVariant(array, length, is_array, touched) {
// 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
......@@ -92,6 +93,7 @@ function UseSparseVariant(array, length, is_array, touched) {
(touched > estimated_elements * 4);
}
// TODO(pwong): Remove once TypedArray.prototype.join() is ported to Torque.
function Stack() {
this.length = 0;
this.values = new InternalArray();
......@@ -123,6 +125,7 @@ function StackHas(stack, v) {
// join invocations.
var visited_arrays = new Stack();
// TODO(pwong): Remove once TypedArray.prototype.join() is ported to Torque.
function DoJoin(
array, length, is_array, separator, use_locale, locales, options) {
if (UseSparseVariant(array, length, is_array, length)) {
......@@ -155,6 +158,7 @@ function DoJoin(
}
}
// TODO(pwong): Remove once TypedArray.prototype.join() is ported to Torque.
function Join(array, length, separator, use_locale, locales, options) {
if (length === 0) return '';
......@@ -178,7 +182,7 @@ function Join(array, length, separator, use_locale, locales, options) {
}
}
// TODO(pwong): Remove once TypedArray.prototype.join() is ported to Torque.
function ConvertToString(use_locale, x, locales, options) {
if (IS_NULL_OR_UNDEFINED(x)) return '';
if (use_locale) {
......@@ -196,48 +200,13 @@ function ConvertToString(use_locale, x, locales, options) {
// -------------------------------------------------------------------
var ArrayJoin;
DEFINE_METHOD(
GlobalArray.prototype,
toString() {
var array;
var func;
if (IS_ARRAY(this)) {
func = this.join;
if (func === ArrayJoin) {
return Join(this, this.length, ',', false);
}
array = this;
} else {
array = TO_OBJECT(this);
func = array.join;
}
if (!IS_CALLABLE(func)) {
return %_Call(ObjectToString, array);
}
return %_Call(func, array);
}
);
// ecma402 #sup-array.prototype.tolocalestring
// TODO(pwong): Remove once TypedArray.prototype.join() is ported to Torque.
function InnerArrayToLocaleString(array, length, locales, options) {
return Join(array, TO_LENGTH(length), ',', true, locales, options);
}
DEFINE_METHOD(
GlobalArray.prototype,
// ecma402 #sup-array.prototype.tolocalestring
toLocaleString() {
var array = TO_OBJECT(this);
var arrayLen = array.length;
var locales = arguments[0];
var options = arguments[1];
return InnerArrayToLocaleString(array, arrayLen, locales, options);
}
);
// TODO(pwong): Remove once TypedArray.prototype.join() is ported to Torque.
function InnerArrayJoin(separator, array, length) {
if (IS_UNDEFINED(separator)) {
separator = ',';
......@@ -256,15 +225,6 @@ function InnerArrayJoin(separator, array, length) {
}
DEFINE_METHOD(
GlobalArray.prototype,
join(separator) {
var array = TO_OBJECT(this);
var length = TO_LENGTH(array.length);
return InnerArrayJoin(separator, array, length);
}
);
// Oh the humanity... don't remove the following function because js2c for some
......@@ -275,6 +235,7 @@ function ArraySliceFallback(start, end) {
return null;
}
// TODO(pwong): Remove once TypedArray.prototype.join() is ported to Torque.
function InnerArraySort(array, length, comparefn) {
// In-place QuickSort algorithm.
// For short (length <= 10) arrays, insertion sort is used for efficiency.
......@@ -501,7 +462,9 @@ utils.Export(function(to) {
to.ArrayPush = ArrayPush;
to.ArrayToString = ArrayToString;
to.ArrayValues = ArrayValues;
// TODO(pwong): Remove once TypedArray.prototype.join() is ported to Torque.
to.InnerArrayJoin = InnerArrayJoin;
// TODO(pwong): Remove once TypedArray.prototype.join() is ported to Torque.
to.InnerArrayToLocaleString = InnerArrayToLocaleString;
});
......
......@@ -11397,6 +11397,111 @@ Handle<FixedArray> String::CalculateLineEnds(Isolate* isolate,
return array;
}
namespace {
template <typename sinkchar>
void WriteFixedArrayToFlat(FixedArray* fixed_array, int length,
String* separator, sinkchar* sink, int sink_length) {
DisallowHeapAllocation no_allocation;
CHECK_GT(length, 0);
CHECK_LE(length, fixed_array->length());
#ifdef DEBUG
sinkchar* sink_end = sink + sink_length;
#endif
const int separator_length = separator->length();
const bool use_one_byte_separator_fast_path =
separator_length == 1 && sizeof(sinkchar) == 1 &&
StringShape(separator).IsSequentialOneByte();
uint8_t separator_one_char;
if (use_one_byte_separator_fast_path) {
CHECK(StringShape(separator).IsSequentialOneByte());
CHECK_EQ(separator->length(), 1);
separator_one_char = SeqOneByteString::cast(separator)->GetChars()[0];
}
uint32_t num_separators = 0;
for (int i = 0; i < length; i++) {
Object* element = fixed_array->get(i);
const bool element_is_separator_sequence = element->IsSmi();
// If element is a Smi, it represents the number of separators to write.
if (V8_UNLIKELY(element_is_separator_sequence)) {
CHECK(element->ToUint32(&num_separators));
// Verify that Smis (number of separators) only occur when necessary:
// 1) at the beginning
// 2) at the end
// 3) when the number of separators > 1
// - It is assumed that consecutive Strings will have one separator,
// so there is no need for a Smi.
DCHECK(i == 0 || i == length - 1 || num_separators > 1);
}
// Write separator(s) if necessary.
if (num_separators > 0 && separator_length > 0) {
// TODO(pwong): Consider doubling strategy employed by runtime-strings.cc
// WriteRepeatToFlat().
// Fast path for single character, single byte separators.
if (use_one_byte_separator_fast_path) {
DCHECK_LE(sink + num_separators, sink_end);
memset(sink, separator_one_char, num_separators);
DCHECK_EQ(separator_length, 1);
sink += num_separators;
} else {
for (uint32_t j = 0; j < num_separators; j++) {
DCHECK_LE(sink + separator_length, sink_end);
String::WriteToFlat(separator, sink, 0, separator_length);
sink += separator_length;
}
}
}
if (V8_UNLIKELY(element_is_separator_sequence)) {
num_separators = 0;
} else {
DCHECK(element->IsString());
String* string = String::cast(element);
const int string_length = string->length();
DCHECK(string_length == 0 || sink < sink_end);
String::WriteToFlat(string, sink, 0, string_length);
sink += string_length;
// Next string element, needs at least one separator preceding it.
num_separators = 1;
}
}
// Verify we have written to the end of the sink.
DCHECK_EQ(sink, sink_end);
}
} // namespace
// static
String* JSArray::ArrayJoinConcatToSequentialString(Isolate* isolate,
FixedArray* fixed_array,
intptr_t length,
String* separator,
String* dest) {
DisallowHeapAllocation no_allocation;
DisallowJavascriptExecution no_js(isolate);
DCHECK(fixed_array->IsFixedArray());
DCHECK(StringShape(dest).IsSequentialOneByte() ||
StringShape(dest).IsSequentialTwoByte());
if (StringShape(dest).IsSequentialOneByte()) {
WriteFixedArrayToFlat(fixed_array, static_cast<int>(length), separator,
SeqOneByteString::cast(dest)->GetChars(),
dest->length());
} else {
DCHECK(StringShape(dest).IsSequentialTwoByte());
WriteFixedArrayToFlat(fixed_array, static_cast<int>(length), separator,
SeqTwoByteString::cast(dest)->GetChars(),
dest->length());
}
return dest;
}
// Compares the contents of two strings by reading and comparing
// int-sized blocks of characters.
......
......@@ -63,6 +63,28 @@ class JSArray : public JSObject {
Isolate* isolate, Handle<JSArray> a, PropertyDescriptor* desc,
ShouldThrow should_throw);
// Support for Array.prototype.join().
// Writes a fixed array of strings and separators to a single destination
// string. This helpers assumes the fixed array encodes separators in two
// ways:
// 1) Explicitly with a smi, whos value represents the number of repeated
// separators.
// 2) Implicitly between two consecutive strings a single separator.
//
// Here are some input/output examples given the separator string is ',':
//
// [1, 'hello', 2, 'world', 1] => ',hello,,world,'
// ['hello', 'world'] => 'hello,world'
//
// To avoid any allocations, this helper assumes the destination string is the
// exact length necessary to write the strings and separators from the fixed
// array.
static String* ArrayJoinConcatToSequentialString(Isolate* isolate,
FixedArray* fixed_array,
intptr_t length,
String* separator,
String* dest);
// Checks whether the Array has the current realm's Array.prototype as its
// prototype. This function is best-effort and only gives a conservative
// approximation, erring on the side of false, in particular with respect
......@@ -90,6 +112,9 @@ class JSArray : public JSObject {
// This constant is somewhat arbitrary. Any large enough value would work.
static const uint32_t kMaxFastArrayLength = 32 * 1024 * 1024;
// Min. stack size for detecting an Array.prototype.join() call cycle.
static const uint32_t kMinJoinStackSize = 2;
static const int kInitialMaxFastElementArray =
(kMaxRegularHeapObjectSize - FixedArray::kHeaderSize - kSize -
AllocationMemento::kSize) >>
......
......@@ -340,6 +340,7 @@ RUNTIME_FUNCTION(Runtime_StringBuilderConcat) {
}
}
// TODO(pwong): Remove once TypedArray.prototype.join() is ported to Torque.
RUNTIME_FUNCTION(Runtime_StringBuilderJoin) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
......@@ -444,6 +445,7 @@ static void WriteRepeatToFlat(String* src, Vector<sinkchar> buffer, int cursor,
}
}
// TODO(pwong): Remove once TypedArray.prototype.join() is ported to Torque.
template <typename Char>
static void JoinSparseArrayWithSeparator(FixedArray* elements,
int elements_length,
......@@ -480,6 +482,7 @@ static void JoinSparseArrayWithSeparator(FixedArray* elements,
DCHECK(cursor <= buffer.length());
}
// TODO(pwong): Remove once TypedArray.prototype.join() is ported to Torque.
RUNTIME_FUNCTION(Runtime_SparseJoinWithSeparator) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
......
......@@ -73,8 +73,8 @@ function listener(event, exec_state, event_data, data) {
"flatMap", "forEach", "every", "some", "reduce", "reduceRight", "find",
"filter", "map", "findIndex"
];
var fails = ["toString", "join", "toLocaleString", "pop", "push", "reverse",
"shift", "unshift", "splice", "sort", "copyWithin", "fill"];
var fails = ["toLocaleString", "pop", "push", "reverse", "shift", "unshift",
"splice", "sort", "copyWithin", "fill"];
for (f of Object.getOwnPropertyNames(Array.prototype)) {
if (typeof Array.prototype[f] === "function") {
if (fails.includes(f)) {
......
......@@ -170,20 +170,6 @@ for (var use_real_arrays = 0; use_real_arrays <= 1; use_real_arrays++) {
assertEquals("concat", join);
join = ba.join('');
assertEquals("catcon", join);
var sparse = [];
sparse[pos + 1000] = 'is ';
sparse[pos + 271828] = 'time ';
sparse[pos + 31415] = 'the ';
sparse[pos + 012260199] = 'all ';
sparse[-1] = 'foo';
sparse[pos + 22591927] = 'good ';
sparse[pos + 1618033] = 'for ';
sparse[pos + 91] = ': Now ';
sparse[pos + 86720199] = 'men.';
sparse.hest = 'fisk';
assertEquals("baz: Now is the time for all good men.", sparse.join(''));
}
a = new_function(pos);
......
// 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.
(function ArrayPrototypeChanged() {
const el = {
toString() {
Array.prototype[1] = '2';
return '1';
}
};
const a = [el, ,3];
assertSame("123", a.join(''));
})();
// 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
const MIN_DICTIONARY_INDEX = 8192;
function ArrayTests() {
(function ToStringThrows() {
function TestError() {}
let callCount = 0;
const toStringThrows = {
toString() {
callCount++;
throw new TestError;
}
};
const a = [toStringThrows];
assertThrows(() => a.join(), TestError);
assertSame(1, callCount);
// Verifies cycle detection still works properly after thrown error.
a[0] = 1;
a[1] = 2;
assertSame('1,2', a.join());
})();
(function ArrayLengthIncreased() {
let callCount = 0;
const a = [
{
toString() {
callCount++;
a.push(2);
return '1';
}
}
];
assertSame('1', a.join());
assertSame(1, callCount);
assertSame('1,2', a.join());
})();
(function ArrayLengthDecreased() {
let callCount = 0;
const a = [
{
toString() {
callCount++;
a.pop();
return '1';
}
},
'2'
];
assertSame('1,', a.join());
assertSame(1, callCount);
assertSame('1', a.join());
})();
(function ElementsKindChangedToHoley() {
let callCount = 0;
const a = [
{
toString() {
callCount++;
a.length = 4;
a[1] = 777;
a[2] = 7.7;
return '1';
}
},
2,
3
];
assertSame('1,777,7.7', a.join());
assertSame(1, callCount);
assertSame('1,777,7.7,', a.join());
})();
(function ElementsKindChangedToHoleyThroughDeletion() {
let callCount = 0;
const a = [
{
toString() {
callCount++;
delete a[1];
a[2] = 7.7;
return '1';
}
},
2,
3
];
assertSame('1,,7.7', a.join());
assertSame(1, callCount);
assertSame('1,,7.7', a.join());
})();
(function NumberDictionaryChanged() {
let callCount = 0;
const a = [];
a[MIN_DICTIONARY_INDEX - 1] = {
toString() {
callCount++;
a[MIN_DICTIONARY_INDEX] = '2';
return '1';
}
};
a[MIN_DICTIONARY_INDEX] = 'NOPE';
assertTrue(%HasDictionaryElements(a));
assertSame('12', a.join(''));
assertSame(1, callCount);
assertSame('12', a.join(''));
})();
(function NumberDictionaryLengthChange() {
let callCount = 0;
const a = [];
a[MIN_DICTIONARY_INDEX - 1] = {
toString() {
callCount++;
a.length = MIN_DICTIONARY_INDEX;
return '1';
}
};
a[MIN_DICTIONARY_INDEX] = '2';
assertTrue(%HasDictionaryElements(a));
assertSame('1', a.join(''));
assertSame(1, callCount);
assertSame('1', a.join(''));
})();
}
(function NonArrayCycleDetection() {
const a = {
length: 3,
toString() { return Array.prototype.join.call(this); }
};
a[0] = '1';
a[1] = a;
a[2] = '3';
assertSame("1,,3", Array.prototype.join.call(a));
});
ArrayTests();
%SetForceSlowPath(true);
ArrayTests();
// 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
(function Throws() {
function TestError() {}
let callCount = 0;
const a = [0, 1];
Object.defineProperty(a, '0', {
configurable: true,
get() {
callCount++;
throw new TestError();
}
});
assertTrue(%HasDictionaryElements(a));
assertThrows(() => a.join(), TestError);
assertSame(1, callCount);
// Verifies cycle detection still works properly after thrown error.
Object.defineProperty(a, '0', {
configurable: true,
get() {
callCount++;
return 777;
}
});
assertSame('777,1', a.join());
assertSame(2, callCount);
})();
(function ArrayLengthIncreased() {
let callCount = 0;
const a = [1];
Object.defineProperty(a, '0', {
configurable: true,
get() {
callCount++;
a.push(2);
return 9;
}
});
assertSame('9', a.join());
assertSame(1, callCount);
// Verifies cycle detection still works properly after continuation.
assertSame('9,2', a.join());
})();
(function ArrayLengthDecreased() {
let callCount = 0;
const a = [0, 1];
Object.defineProperty(a, '0', {
configurable: true,
get() {
callCount++;
a.length = 1;
return 9;
}
});
assertSame('9,', a.join());
assertSame(1, callCount);
// Verifies cycle detection still works properly after continuation.
assertSame('9', a.join());
})();
(function ElementsKindChangedToHoley() {
let callCount = 0;
const a = [0, 1];
Object.defineProperty(a, '0', {
configurable: true,
get() {
callCount++;
a.length = 3;
return 9;
}
});
assertSame('9,1', a.join());
assertSame(1, callCount);
// Verifies cycle detection still works properly after continuation.
assertSame('9,1,', a.join());
})();
// 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.
const DEPTH = 128;
function makeNestedArray(depth, value) {
return depth > 0 ? [value, makeNestedArray(depth - 1, value)] : [value];
}
const array = makeNestedArray(DEPTH, 'a');
const expected = 'a' + ',a'.repeat(DEPTH);
assertSame(expected, array.join());
// Verify cycle detection is still working.
assertSame(expected, array.join());
// 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.
(function Throws() {
function TestError() {}
let callCount = 0;
const a = {
0: 1,
1: 2,
get length() {
callCount++;
throw new TestError();
}
};
assertThrows(() => Array.prototype.join.call(a), TestError);
assertSame(1, callCount);
// Verifies cycle detection still works properly after thrown error.
Object.defineProperty(a, 'length', {
get() {
callCount++;
return 2;
}
});
assertSame('1,2', Array.prototype.join.call(a));
assertSame(2, callCount);
})();
// 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
const MIN_DICTIONARY_INDEX = 8192;
(function ToStringThrows() {
function TestError() {}
let callCount = 0;
const a = [1, 2];
assertThrows(() => a.join({
toString() {
callCount++;
throw new TestError;
}
}), TestError);
assertSame(1, callCount);
// Verifies cycle detection still works properly after thrown error.
assertSame('1,2', a.join());
})();
(function RecursiveJoinCall() {
const a = [1,2,3];
let callCount = 0;
const sep = {
toString() {
callCount++;
return a.join('-');
}
};
assertSame('11-2-321-2-33', a.join(sep));
assertSame(1, callCount);
// Verify cycle detection works properly after nested call
assertSame('1,2,3', a.join());
})();
(function ArrayLengthIncreased() {
const a = [1,2,3];
let callCount = 0;
assertSame('1,2,3', a.join({
toString() {
callCount++;
a.push(4);
return ',';
}
}));
assertSame(1, callCount);
assertSame('1,2,3,4', a.join());
})();
(function ArrayLengthDecreased() {
const a = [1,2,3];
let callCount = 0;
assertSame('1,2,', a.join({
toString() {
callCount++;
a.pop();
return ',';
}
}));
assertSame(1, callCount);
assertSame('1,2', a.join());
})();
(function ArrayEmptied() {
const a = [1,2,3];
let callCount = 0;
assertSame(',,', a.join({
toString() {
callCount++;
a.length = 0;
return ',';
}
}));
assertSame(1, callCount);
})();
(function NumberDictionaryEmptied() {
const a = [];
a[0] = 1;
a[MIN_DICTIONARY_INDEX] = 2;
assertTrue(%HasDictionaryElements(a));
let callCount = 0;
assertSame('-'.repeat(MIN_DICTIONARY_INDEX), a.join({
toString() {
callCount++;
a.length = 0;
return '-';
}
}));
assertSame(1, callCount);
})();
(function NumberDictionaryEmptiedEmptySeparator() {
const a = [];
a[0] = 1;
a[MIN_DICTIONARY_INDEX] = 2;
assertTrue(%HasDictionaryElements(a));
let callCount = 0;
assertSame(''.repeat(MIN_DICTIONARY_INDEX), a.join({
toString() {
callCount++;
a.length = 0;
return '';
}
}));
assertSame(1, callCount);
})();
(function ElementsKindSmiToDoubles() {
const a = [1,2,3];
let callCount = 0;
assertTrue(%HasSmiElements(a));
assertSame('1.5,2,3', a.join({
toString() {
callCount++;
a[0] = 1.5;
assertTrue(%HasDoubleElements(a));
return ',';
}
}));
assertSame(1, callCount);
assertSame('1.5,2,3', a.join());
})();
(function ElementsKindDoublesToObjects() {
const a = [1.5, 2.5, 3.5];
let callCount = 0;
assertTrue(%HasDoubleElements(a));
assertSame('one,2.5,3.5', a.join({
toString() {
callCount++;
a[0] = 'one';
assertTrue(%HasObjectElements(a));
return ',';
}
}));
assertSame(1, callCount);
assertSame('one,2.5,3.5', a.join());
})();
(function ArrayIsNoLongerFast() {
const a = [1,2,3];
let callCount = 0;
assertSame('666,2,3', a.join({
toString() {
callCount++;
Object.defineProperty(a, '0', {
get(){ return 666; }
});
return ',';
}
}));
assertSame(1, callCount);
assertSame('666,2,3', a.join());
})();
(function ArrayPrototypeUnset() {
const a = [1,2];
a.length = 3;
let callCount = 0;
assertSame('1,2,4', a.join({
toString() {
callCount++;
a.__proto__ = { '2': 4 };
return ',';
}
}));
assertSame(1, callCount);
a.__proto__ = Array.prototype;
assertSame('1,2,', a.join());
})();
(function ArrayPrototypeIsNoLongerFast() {
const a = [1,2,3];
let callCount = 0;
assertSame('1,2,777', a.join({
toString() {
callCount++;
a.pop();
Object.defineProperty(Array.prototype, '2', {
get(){ return 777; }
});
return ',';
}
}));
assertSame(1, callCount);
assertSame('1,2', a.join());
})();
......@@ -25,6 +25,17 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
assertSame(',', [null, undefined].join());
assertSame('1.5,2.5', [1.5, 2.5].join());
assertSame(',1.5,', [,1.5,,].join());
var obj = {
toString() {
return 'a';
}
};
assertSame('1,1.5,a,', [1, 1.5, obj, ,].join());
// Test that array join calls toString on subarrays.
var a = [[1,2],3,4,[5,6]];
assertEquals('1,2345,6', a.join(''));
......@@ -82,9 +93,6 @@ assertEquals(246244, 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;
......
// 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.
(function CycleDetection() {
const arr = [
{
toLocaleString() {
return [1, arr];
}
}
];
assertSame('1,', arr.toLocaleString());
assertSame('1,', arr.toLocaleString());
})();
(function ThrowsError(){
function TestError() {}
const arr = [];
const obj = {
toLocaleString(){
throw new TestError();
}
};
arr[0] = obj;
assertThrows(() => arr.toLocaleString(), TestError);
// Verifies cycle detection still works properly after thrown error.
arr[0] = {
toLocaleString() {
return 1;
}
};
assertSame('1', arr.toLocaleString());
})();
(function AccessThrowsError(){
function TestError() {}
const arr = [];
const obj = {
get toLocaleString(){
throw new TestError();
}
};
arr[0] = obj;
assertThrows(() => arr.toLocaleString(), TestError);
// Verifies cycle detection still works properly after thrown error.
arr[0] = {
toLocaleString() {
return 1;
}
};
assertSame('1', arr.toLocaleString());
})();
(function NotCallable(){
const arr = [];
const obj = {
toLocaleString: 7
}
arr[0] = obj;
assertThrows(() => arr.toLocaleString(), TypeError, '7 is not a function');
// Verifies cycle detection still works properly after thrown error.
arr[0] = {
toLocaleString() {
return 1;
}
};
assertSame('1', arr.toLocaleString());
})();
......@@ -28,10 +28,10 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE
PASS [1].toString() is '1'
PASS [1].toLocaleString() is 'toLocaleString'
FAIL [1].toLocaleString() should be 1. Threw exception TypeError: string "invalid" is not a function
FAIL [1].toLocaleString() should be 1. Threw exception TypeError: invalid is not a function
PASS [/r/].toString() is 'toString2'
PASS [/r/].toLocaleString() is 'toLocaleString2'
FAIL [/r/].toLocaleString() should be toString2. Threw exception TypeError: string "invalid" is not a function
FAIL [/r/].toLocaleString() should be toString2. Threw exception TypeError: invalid is not a function
PASS caught is true
PASS successfullyParsed is true
......
......@@ -417,7 +417,7 @@ KNOWN_OBJECTS = {
("OLD_SPACE", 0x018c1): "RegExpMultipleCache",
("OLD_SPACE", 0x020d1): "DefaultMicrotaskQueue",
("OLD_SPACE", 0x020e9): "BuiltinsConstantsTable",
("OLD_SPACE", 0x029f9): "HashSeed",
("OLD_SPACE", 0x02a11): "HashSeed",
}
# List of known V8 Frame Markers.
......
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