[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: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#56699}
This commit is contained in:
peterwmwong 2018-10-16 07:52:25 -05:00 committed by Commit Bot
parent f64edae08f
commit 952c097679
29 changed files with 1512 additions and 94 deletions

View File

@ -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",

View File

@ -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 ---

526
src/builtins/array-join.tq Normal file
View File

@ -0,0 +1,526 @@
// 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.
module array {
type LoadJoinElementFn = builtin(Context, JSReceiver, Number) => Object;
// Fast C call to write a fixed array (see Buffer.fixedArray) to a single
// string.
extern macro CallJSArrayArrayJoinConcatToSequentialString(
FixedArray, intptr, String, String): String;
extern macro CallLoadJoinElement(implicit context: Context)(
LoadJoinElementFn, JSReceiver, Number): Object
labels IfException(Object);
extern macro CallConvertToLocaleString(implicit context: Context)(
Object, Object, Object): String
labels IfException(Object);
extern macro CallToString(implicit context: Context)(Object): String
labels IfException(Object);
builtin LoadJoinElement<T: type>(
context: Context, receiver: JSReceiver, k: Number): Object {
return GetProperty(receiver, k);
}
LoadJoinElement<DictionaryElements>(
context: Context, receiver: JSReceiver, k: Number): Object {
const array: JSArray = UnsafeCast<JSArray>(receiver);
const dict: NumberDictionary = UnsafeCast<NumberDictionary>(array.elements);
try {
return BasicLoadNumberDictionaryElement(dict, Signed(Convert<uintptr>(k)))
otherwise IfNoData, IfHole;
}
label IfNoData deferred {
return GetProperty(receiver, k);
}
label IfHole {
return kEmptyString;
}
}
LoadJoinElement<FastSmiOrObjectElements>(
context: Context, receiver: JSReceiver, k: Number): Object {
const array: JSArray = UnsafeCast<JSArray>(receiver);
const fixedArray: FixedArray = UnsafeCast<FixedArray>(array.elements);
const element: Object = fixedArray[UnsafeCast<Smi>(k)];
return element == Hole ? kEmptyString : element;
}
LoadJoinElement<FastDoubleElements>(
context: Context, receiver: JSReceiver, k: Number): Object {
const array: JSArray = UnsafeCast<JSArray>(receiver);
const fixedDoubleArray: FixedDoubleArray =
UnsafeCast<FixedDoubleArray>(array.elements);
try {
const element: float64 = LoadDoubleWithHoleCheck(
fixedDoubleArray, UnsafeCast<Smi>(k)) otherwise IfHole;
return AllocateHeapNumberWithValue(element);
}
label IfHole {
return kEmptyString;
}
}
builtin ConvertToLocaleString(
context: Context, element: Object, locales: Object,
options: Object): String {
if (IsNullOrUndefined(element)) return kEmptyString;
const prop: Object = GetProperty(element, 'toLocaleString');
try {
const callable: Callable = Cast<Callable>(prop) otherwise TypeError;
let result: Object;
if (IsNullOrUndefined(locales)) {
result = Call(context, callable, element);
} else if (IsNullOrUndefined(options)) {
result = Call(context, callable, element, locales);
} else {
result = Call(context, callable, element, locales, options);
}
return ToString_Inline(context, result);
}
label TypeError {
ThrowTypeError(context, kCalledNonCallable, prop);
}
}
// Verifies the current element JSArray accessor can still be safely used
// (see LoadJoinElement<ElementsAccessor>).
macro CannotUseSameArrayAccessor(implicit context: Context)(
originalMap: Object, originalLen: Object, receiver: JSReceiver): never
labels Cannot, Can {
const array: JSArray = UnsafeCast<JSArray>(receiver);
if (originalMap != array.map) goto Cannot;
if (originalLen != array.length) goto Cannot;
if (IsNoElementsProtectorCellInvalid()) goto Cannot;
goto Can;
}
// Calculates the running total length of the resulting string. If the
// calculated length exceeds the maximum string length (see
// String::kMaxLength), throws a range error.
macro AddStringLength(implicit context: Context)(
lenA: intptr, lenB: intptr): intptr {
try {
const length: intptr = TryIntPtrAdd(lenA, lenB) otherwise IfOverflow;
if (length > kStringMaxLength) goto IfOverflow;
return length;
}
label IfOverflow deferred {
ThrowRangeError(context, kInvalidStringLength);
}
}
// Contains the information necessary to create a single, separator delimited,
// flattened one or two byte string.
// The buffer is maintained and updated by BufferInit(), BufferAdd(),
// BufferAddSeparators().
struct Buffer {
// Fixed array holding elements that are either:
// 1) String result of `ToString(next)`.
// 2) Smi representing the number of consecutive separators.
// `BufferJoin()` will iterate and writes these entries to a flat string.
//
// To save space, reduce reads and writes, only separators at the beginning,
// end, or more than one are written.
//
// No hole example
// receiver: ['hello', 'world']
// fixedArray: ['hello', 'world']
//
// Hole example
// receiver: [<hole>, 'hello', <hole>, 'world', <hole>]
// fixedArray: [1, 'hello', 2, 'world', 1]
fixedArray: FixedArray;
// Index to insert a new entry into `fixedArray`.
index: intptr;
// Running total of the resulting string length.
totalStringLength: intptr;
// `true` if the separator and all strings in the buffer are one-byte,
// otherwise `false`.
isOneByte: bool;
}
macro BufferInit(estimatedNonHoleyElements: uintptr, sep: String): Buffer {
const bufferSize: intptr = Signed(estimatedNonHoleyElements + 1);
const cappedBufferSize: intptr = bufferSize > kFixedArrayMaxLength ?
FromConstexpr<intptr>(kFixedArrayMaxLength) :
bufferSize;
const fixedArray: FixedArray = AllocateZeroedFixedArray(cappedBufferSize);
const isOneByte: bool = HasOnlyOneByteChars(sep.instanceType);
return Buffer{fixedArray, 0, 0, isOneByte};
}
macro BufferAdd(implicit context: Context)(
initialBuffer: Buffer, str: String, nofSeparators: uintptr,
separatorLength: intptr): Buffer {
let buffer: Buffer = initialBuffer;
// Add separators if necessary (at the beginning or more than one)
const writeSeparators: bool = buffer.index == 0 | nofSeparators > 1;
buffer = BufferAddSeparators(
buffer, nofSeparators, separatorLength, writeSeparators);
const totalStringLength: intptr =
AddStringLength(buffer.totalStringLength, str.length);
let index: intptr = buffer.index;
assert(index < buffer.fixedArray.length_intptr);
buffer.fixedArray[index++] = str;
const isOneByte: bool =
HasOnlyOneByteChars(str.instanceType) & buffer.isOneByte;
return Buffer{buffer.fixedArray, index, totalStringLength, isOneByte};
}
macro BufferAddSeparators(implicit context: Context)(
buffer: Buffer, nofSeparators: uintptr, separatorLength: intptr,
write: bool): Buffer {
if (nofSeparators == 0 || separatorLength == 0) return buffer;
const nofSeparatorsInt: intptr = Signed(nofSeparators);
const sepsLen: intptr = separatorLength * nofSeparatorsInt;
// Detect integer overflow
if (nofSeparatorsInt <= 0 || sepsLen <= 0) deferred {
ThrowRangeError(context, kInvalidStringLength);
}
const totalStringLength: intptr =
AddStringLength(buffer.totalStringLength, sepsLen);
let index: intptr = buffer.index;
if (write) deferred {
assert(index < buffer.fixedArray.length_intptr);
buffer.fixedArray[index++] = Convert<Smi>(nofSeparatorsInt);
}
return Buffer{
buffer.fixedArray,
index,
totalStringLength,
buffer.isOneByte
};
}
macro BufferJoin(implicit context: Context)(
buffer: Buffer, sep: String): String {
assert(IsValidPositiveSmi(buffer.totalStringLength));
if (buffer.totalStringLength == 0) return kEmptyString;
const length: uint32 = Convert<uint32>(Unsigned(buffer.totalStringLength));
const r: String = buffer.isOneByte ? AllocateSeqOneByteString(length) :
AllocateSeqTwoByteString(length);
return CallJSArrayArrayJoinConcatToSequentialString(
buffer.fixedArray, buffer.index, sep, r);
}
macro ArrayJoinImpl(
context: Context, receiver: JSReceiver, sep: String, lengthNumber: Number,
useToLocaleString: constexpr bool, locales: Object, options: Object,
estimatedNonHoleyElements: uintptr,
initialLoadJoinElement: LoadJoinElementFn): String
labels IfException(Object) {
const initialMap: Map = receiver.map;
const len: uintptr = Convert<uintptr>(lengthNumber);
const separatorLength: intptr = sep.length;
let nofSeparators: uintptr = 0;
let loadJoinElements: LoadJoinElementFn = initialLoadJoinElement;
let buffer: Buffer = BufferInit(estimatedNonHoleyElements, sep);
// 6. Let k be 0.
let k: uintptr = 0;
if (estimatedNonHoleyElements != 0) {
// 7. Repeat, while k < len
while (k < len) {
if (k > 0) {
// a. If k > 0, let R be the string-concatenation of R and sep.
nofSeparators = nofSeparators + 1;
// Verify the current LoadJoinElement specialization can safely be
// used. Otherwise, fall back to generic element access (see
// LoadJoinElement<GenericElementsAccessor>).
if (loadJoinElements != LoadJoinElement<GenericElementsAccessor>&&
CannotUseSameArrayAccessor(initialMap, lengthNumber, receiver))
deferred {
loadJoinElements = LoadJoinElement<GenericElementsAccessor>;
const temp: String = BufferJoin(buffer, sep);
buffer = BufferInit((len - k), sep);
buffer = BufferAdd(buffer, temp, 0, separatorLength);
}
}
// b. Let element be ? Get(O, ! ToString(k)).
const element: Object = CallLoadJoinElement(
loadJoinElements, receiver, Convert<Number>(k++))
otherwise IfException;
// c. If element is undefined or null, let next be the empty String;
// otherwise, let next be ? ToString(element).
let next: String;
if constexpr (useToLocaleString) {
next = CallConvertToLocaleString(element, locales, options)
otherwise IfException;
if (next == kEmptyString) continue;
} else {
typeswitch (element) {
case (str: String): {
if (str == kEmptyString) continue;
next = str;
}
case (num: Number): {
next = NumberToString(num);
}
case (obj: HeapObject): {
if (IsNullOrUndefined(obj)) continue;
next = CallToString(obj) otherwise IfException;
}
}
}
// d. Set R to the string-concatenation of R and next.
buffer = BufferAdd(buffer, next, nofSeparators, separatorLength);
nofSeparators = 0;
}
} else {
assert(len > k);
nofSeparators = len - k - 1;
}
// Add any separators at the end.
buffer = BufferAddSeparators(buffer, nofSeparators, separatorLength, true);
// 8. Return R.
return BufferJoin(buffer, sep);
}
macro ArrayJoin(implicit context: Context)(
useToLocaleString: constexpr bool, receiver: JSReceiver, sep: String,
lenNumber: Number, locales: Object, options: Object): Object
labels IfException(Object) {
const map: Map = receiver.map;
const kind: ElementsKind = map.elements_kind;
const len: uintptr = Convert<uintptr>(lenNumber);
// Estimated number of elements that are not holes. This is conservatively
// defaulted to `len`. When the receiver has dictionary elements, a better
// estimate can be determined through GetNumberDictionaryNumberOfElements.
let estimatedNonHoleyElements: uintptr = len;
let loadJoinElements: LoadJoinElementFn;
try {
const array: JSArray = Cast<JSArray>(receiver) otherwise IfSlowPath;
if (array.length != lenNumber) goto IfSlowPath;
if (!IsPrototypeInitialArrayPrototype(context, map)) goto IfSlowPath;
if (IsNoElementsProtectorCellInvalid()) goto IfSlowPath;
if (IsElementsKindLessThanOrEqual(kind, HOLEY_ELEMENTS)) {
loadJoinElements = LoadJoinElement<FastSmiOrObjectElements>;
} else if (IsElementsKindLessThanOrEqual(kind, HOLEY_DOUBLE_ELEMENTS)) {
loadJoinElements = LoadJoinElement<FastDoubleElements>;
} else if (kind == DICTIONARY_ELEMENTS) {
const dict: NumberDictionary =
UnsafeCast<NumberDictionary>(array.elements);
estimatedNonHoleyElements =
Unsigned(Convert<intptr>(GetNumberDictionaryNumberOfElements(dict)))
<< 1;
loadJoinElements = LoadJoinElement<DictionaryElements>;
} else {
goto IfSlowPath;
}
}
label IfSlowPath {
loadJoinElements = LoadJoinElement<GenericElementsAccessor>;
}
return ArrayJoinImpl(
context, receiver, sep, lenNumber, useToLocaleString, locales, options,
estimatedNonHoleyElements, loadJoinElements)
otherwise IfException;
}
// The Join Stack detects cyclical calls to Array Join builtins
// (Array.p.join(), Array.p.toString(), Array.p.toLocaleString()). This
// FixedArray holds a stack of receivers to the current call.
// CycleProtectedArrayJoin() is responsible for calling JoinStackPush and
// JoinStackPop when visiting and leaving a receiver, respectively.
const kMinJoinStackSize:
constexpr int31 generates 'JSArray::kMinJoinStackSize';
macro LoadJoinStack(implicit context: Context)(): FixedArray
labels IfUninitialized {
const nativeContext: NativeContext = LoadNativeContext(context);
const stack: HeapObject =
UnsafeCast<HeapObject>(nativeContext[ARRAY_JOIN_STACK_INDEX]);
if (stack == Undefined) goto IfUninitialized;
assert(IsFixedArray(stack));
return UnsafeCast<FixedArray>(stack);
}
macro SetJoinStack(implicit context: Context)(stack: FixedArray): void {
const nativeContext: NativeContext = LoadNativeContext(context);
nativeContext[ARRAY_JOIN_STACK_INDEX] = stack;
}
// Adds a receiver to the stack. The FixedArray will automatically grow to
// accommodate the receiver. If the receiver already exists on the stack,
// this indicates a cyclical call and False is returned.
builtin JoinStackPush(implicit context: Context)(
stack: FixedArray, receiver: JSReceiver): Boolean {
const capacity: intptr = stack.length_intptr;
for (let i: intptr = 0; i < capacity; i++) {
const previouslyVisited: Object = stack[i];
// Add `receiver` to the first open slot
if (previouslyVisited == Hole) {
stack[i] = receiver;
return True;
}
// Detect cycles
if (receiver == previouslyVisited) return False;
}
// If no open slots were found, grow the stack and add receiver to the end.
const newCapacity: intptr = CalculateNewElementsCapacity(capacity);
const newStack: FixedArray =
ExtractFixedArray(stack, 0, capacity, newCapacity, kFixedArrays);
newStack[capacity] = receiver;
SetJoinStack(newStack);
return True;
}
// Fast path the common non-nested calls.
macro JoinStackPushInline(implicit context: Context)(receiver: JSReceiver)
labels CycleDetected {
try {
const stack: FixedArray = LoadJoinStack()
otherwise IfUninitialized;
if (stack[0] == Hole) {
stack[0] = receiver;
} else if (JoinStackPush(stack, receiver) == False)
deferred {
goto CycleDetected;
}
}
label IfUninitialized {
const stack: FixedArray =
AllocateFixedArrayWithHoles(kMinJoinStackSize, kNone);
stack[0] = receiver;
SetJoinStack(stack);
}
}
// Removes a receiver from the stack. The FixedArray will automatically shrink
// to Heap::kMinJoinStackSize once the stack becomes empty.
builtin JoinStackPop(implicit context: Context)(
stack: FixedArray, receiver: JSReceiver): Object {
const len: intptr = stack.length_intptr;
for (let i: intptr = 0; i < len; i++) {
if (stack[i] == receiver) {
// Shrink the Join Stack if the stack will be empty and is larger than
// the minimum size.
if (i == 0 && len > kMinJoinStackSize) deferred {
const newStack: FixedArray =
AllocateFixedArrayWithHoles(kMinJoinStackSize, kNone);
SetJoinStack(newStack);
}
else {
stack[i] = Hole;
}
return Undefined;
}
}
unreachable;
}
// Fast path the common non-nested calls.
macro JoinStackPopInline(implicit context: Context)(receiver: JSReceiver) {
const stack: FixedArray = LoadJoinStack()
otherwise unreachable;
const len: intptr = stack.length_intptr;
// Builtin call was not nested (receiver is the first entry) and
// did not contain other nested arrays that expanded the stack.
if (stack[0] == receiver && len == kMinJoinStackSize) {
StoreFixedArrayElement(stack, 0, Hole, SKIP_WRITE_BARRIER);
} else
deferred {
JoinStackPop(stack, receiver);
}
}
// Main entry point for all builtins using Array Join functionality.
macro CycleProtectedArrayJoin(implicit context: Context)(
useToLocaleString: constexpr bool, receiver: Object, sepObj: Object,
locales: Object, options: Object): Object {
// 1. Let O be ? ToObject(this value).
const o: JSReceiver = ToObject_Inline(context, receiver);
// 2. Let len be ? ToLength(? Get(O, "length")).
const len: Number = GetLengthProperty(o);
// 3. If separator is undefined, let sep be the single-element String ",".
// 4. Else, let sep be ? ToString(separator).
let sep: String = sepObj == Undefined ? FromConstexpr<String>(',') :
ToString_Inline(context, sepObj);
try {
// Fast paths for zero elements
if (len == 0) goto IfReturnEmpty;
JoinStackPushInline(o) otherwise IfReturnEmpty;
const result: Object =
ArrayJoin(useToLocaleString, o, sep, len, locales, options)
otherwise IfException;
JoinStackPopInline(o);
return result;
}
label IfReturnEmpty {
return kEmptyString;
}
label IfException(e: Object) deferred {
JoinStackPopInline(o);
ReThrow(context, e);
unreachable;
}
}
// https://tc39.github.io/ecma262/#sec-array.prototype.join
javascript builtin ArrayPrototypeJoin(
context: Context, receiver: Object, ...arguments): Object {
const separator: Object = arguments[0];
return CycleProtectedArrayJoin(
false, receiver, separator, Undefined, Undefined);
}
// https://tc39.github.io/ecma262/#sec-array.prototype.toLocaleString
javascript builtin ArrayPrototypeToLocaleString(
context: Context, receiver: Object, ...arguments): Object {
const locales: Object = arguments[0];
const options: Object = arguments[1];
return CycleProtectedArrayJoin(true, receiver, ',', locales, options);
}
// https://tc39.github.io/ecma262/#sec-array.prototype.toString
javascript builtin ArrayPrototypeToString(
context: Context, receiver: Object, ...arguments): Object {
// 1. Let array be ? ToObject(this value).
const array: JSReceiver = ToObject_Inline(context, receiver);
// 2. Let func be ? Get(array, "join").
const prop: Object = GetProperty(array, 'join');
try {
// 3. If IsCallable(func) is false, let func be the intrinsic function
// %ObjProto_toString%.
const func: Callable = Cast<Callable>(prop) otherwise NotCallable;
// 4. Return ? Call(func, array).
return Call(context, func, array);
}
label NotCallable {
return ObjectToString(context, array);
}
}
}

View File

@ -56,6 +56,8 @@ type NumberDictionary extends HeapObject
generates 'TNode<NumberDictionary>';
type NativeContextSlot generates 'TNode<IntPtrT>' constexpr 'int32_t';
const ARRAY_JOIN_STACK_INDEX: constexpr NativeContextSlot
generates 'Context::ARRAY_JOIN_STACK_INDEX';
const FAST_ALIASED_ARGUMENTS_MAP_INDEX: constexpr NativeContextSlot
generates 'Context::FAST_ALIASED_ARGUMENTS_MAP_INDEX';
const SLOW_ALIASED_ARGUMENTS_MAP_INDEX: constexpr NativeContextSlot
@ -66,6 +68,8 @@ const SLOPPY_ARGUMENTS_MAP_INDEX: constexpr NativeContextSlot
generates 'Context::SLOPPY_ARGUMENTS_MAP_INDEX';
extern operator '[]' macro LoadContextElement(
NativeContext, NativeContextSlot): Object;
extern operator '[]=' macro StoreContextElement(
NativeContext, NativeContextSlot, Object): void;
extern operator '[]' macro LoadContextElement(Context, intptr): Object;
extern operator '[]' macro LoadContextElement(Context, Smi): Object;
@ -88,8 +92,8 @@ type WriteBarrierMode
generates 'TNode<Int32T>' constexpr 'WriteBarrierMode';
type MessageTemplate constexpr 'MessageTemplate';
type ToIntegerTruncationMode constexpr 'ToIntegerTruncationMode';
type AllocationFlags constexpr 'AllocationFlags';
const NO_ELEMENTS: constexpr ElementsKind generates 'NO_ELEMENTS';
@ -126,6 +130,13 @@ const BIGUINT64_ELEMENTS:
const BIGINT64_ELEMENTS:
constexpr ElementsKind generates 'BIGINT64_ELEMENTS';
const kNone: constexpr AllocationFlags generates 'kNone';
const kDoubleAlignment:
constexpr AllocationFlags generates 'kDoubleAlignment';
const kPretenured: constexpr AllocationFlags generates 'kPretenured';
const kAllowLargeObjectAllocation:
constexpr AllocationFlags generates 'kAllowLargeObjectAllocation';
type FixedUint8Array extends FixedTypedArray;
type FixedInt8Array extends FixedTypedArray;
type FixedUint16Array extends FixedTypedArray;
@ -149,6 +160,8 @@ const kFixedCOWArrayMapRootIndex:
constexpr RootIndex generates 'RootIndex::kFixedCOWArrayMap';
const kEmptyFixedArrayRootIndex:
constexpr RootIndex generates 'RootIndex::kEmptyFixedArray';
const kTheHoleValueRootIndex:
constexpr RootIndex generates 'RootIndex::kTheHoleValue';
const kInvalidArrayLength: constexpr MessageTemplate
generates 'MessageTemplate::kInvalidArrayLength';
@ -156,8 +169,13 @@ const kCalledNonCallable: constexpr MessageTemplate
generates 'MessageTemplate::kCalledNonCallable';
const kCalledOnNullOrUndefined: constexpr MessageTemplate
generates 'MessageTemplate::kCalledOnNullOrUndefined';
const kInvalidStringLength: constexpr MessageTemplate
generates 'MessageTemplate::kInvalidStringLength';
const kMaxSafeInteger: constexpr float64 generates 'kMaxSafeInteger';
const kStringMaxLength: constexpr int31 generates 'String::kMaxLength';
const kFixedArrayMaxLength:
constexpr int31 generates 'FixedArray::kMaxLength';
const kTruncateMinusZero: constexpr ToIntegerTruncationMode
generates 'ToIntegerTruncationMode::kTruncateMinusZero';
@ -182,6 +200,7 @@ extern macro TrueConstant(): Boolean;
extern macro FalseConstant(): Boolean;
extern macro Int32TrueConstant(): bool;
extern macro Int32FalseConstant(): bool;
extern macro EmptyStringConstant(): String;
extern macro LengthStringConstant(): String;
const Hole: Oddball = TheHoleConstant();
@ -189,6 +208,7 @@ const Null: Oddball = NullConstant();
const Undefined: Oddball = UndefinedConstant();
const True: Boolean = TrueConstant();
const False: Boolean = FalseConstant();
const kEmptyString: String = EmptyStringConstant();
const kLengthString: String = LengthStringConstant();
const true: constexpr bool generates 'true';
@ -260,6 +280,8 @@ extern builtin StringLessThan(Context, String, String): Boolean;
extern macro StrictEqual(Object, Object): Boolean;
extern macro SmiLexicographicCompare(Smi, Smi): Smi;
// TODO(tebbi): This should have a return type of `never`.
extern runtime ReThrow(Context, Object);
extern operator '<' macro Int32LessThan(int32, int32): bool;
extern operator '>' macro Int32GreaterThan(int32, int32): bool;
@ -280,6 +302,10 @@ operator '!=' macro ElementsKindNotEqual(
k1: ElementsKind, k2: ElementsKind): bool {
return !ElementsKindEqual(k1, k2);
}
extern macro IsElementsKindLessThanOrEqual(
ElementsKind, constexpr ElementsKind): bool;
extern macro IsElementsKindGreaterThan(
ElementsKind, constexpr ElementsKind): bool;
extern macro IsFastElementsKind(constexpr ElementsKind): constexpr bool;
extern macro IsDoubleElementsKind(constexpr ElementsKind): constexpr bool;
@ -290,10 +316,12 @@ extern operator '==' macro WordEqual(uintptr, uintptr): bool;
extern operator '!=' macro WordNotEqual(intptr, intptr): bool;
extern operator '!=' macro WordNotEqual(uintptr, uintptr): bool;
extern operator '<' macro IntPtrLessThan(intptr, intptr): bool;
extern operator '<' macro UintPtrLessThan(uintptr, uintptr): bool;
extern operator '>' macro IntPtrGreaterThan(intptr, intptr): bool;
extern operator '<=' macro IntPtrLessThanOrEqual(intptr, intptr): bool;
extern operator '>=' macro IntPtrGreaterThanOrEqual(intptr, intptr): bool;
extern operator '>' macro UintPtrGreaterThan(uintptr, uintptr): bool;
extern operator '<=' macro IntPtrLessThanOrEqual(intptr, intptr): bool;
extern operator '<=' macro UintPtrLessThanOrEqual(uintptr, uintptr): bool;
extern operator '>=' macro IntPtrGreaterThanOrEqual(intptr, intptr): bool;
extern operator '>=' macro UintPtrGreaterThanOrEqual(uintptr, uintptr): bool;
extern operator '==' macro Float64Equal(float64, float64): bool;
@ -302,6 +330,8 @@ extern operator '>' macro Float64GreaterThan(float64, float64): bool;
extern operator '==' macro BranchIfNumberEqual(Number, Number): never
labels Taken, NotTaken;
extern operator '!=' macro BranchIfNumberNotEqual(Number, Number): never
labels Taken, NotTaken;
extern operator '<' macro BranchIfNumberLessThan(Number, Number): never
labels Taken, NotTaken;
extern operator '<=' macro BranchIfNumberLessThanOrEqual(Number, Number): never
@ -324,12 +354,18 @@ extern operator '>>>' macro SmiShr(Smi, constexpr int31): Smi;
extern operator '<<' macro SmiShl(Smi, constexpr int31): Smi;
extern operator '+' macro IntPtrAdd(intptr, intptr): intptr;
extern operator '+' macro UintPtrAdd(uintptr, uintptr): uintptr;
extern operator '-' macro IntPtrSub(intptr, intptr): intptr;
extern operator '>>>' macro WordShr(uintptr, uintptr): uintptr;
extern operator '*' macro IntPtrMul(intptr, intptr): intptr;
extern operator '<<' macro WordShl(intptr, intptr): intptr;
extern operator '&' macro WordAnd(intptr, intptr): intptr;
extern operator '|' macro WordOr(intptr, intptr): intptr;
extern operator '+' macro UintPtrAdd(uintptr, uintptr): uintptr;
extern operator '-' macro UintPtrSub(uintptr, uintptr): uintptr;
extern operator '>>>' macro WordShr(uintptr, uintptr): uintptr;
extern operator '<<' macro WordShl(uintptr, uintptr): uintptr;
extern operator '&' macro WordAnd(uintptr, uintptr): uintptr;
extern operator '|' macro WordOr(uintptr, uintptr): uintptr;
extern operator '+' macro Int32Add(int32, int32): int32;
extern operator '-' macro Int32Sub(int32, int32): int32;
@ -349,6 +385,8 @@ extern operator '<<' macro Word32Shl(int32, int32): int32;
extern operator '<<' macro Word32Shl(uint32, uint32): uint32;
extern operator '|' macro Word32Or(int32, int32): int32;
extern operator '|' macro Word32Or(uint32, uint32): uint32;
extern operator '&' macro Word32And(bool, bool): bool;
extern operator '|' macro Word32Or(bool, bool): bool;
extern operator '+' macro Float64Add(float64, float64): float64;
@ -384,6 +422,7 @@ extern operator '[]' macro GetArgumentValue(
extern macro TaggedIsSmi(Object): bool;
extern macro TaggedIsNotSmi(Object): bool;
extern macro TaggedIsPositiveSmi(Object): bool;
extern macro IsValidPositiveSmi(intptr): bool;
extern macro HeapObjectToJSDataView(HeapObject): JSDataView
labels CastError;
@ -399,6 +438,10 @@ extern macro HeapObjectToFixedArray(HeapObject): FixedArray
labels CastError;
extern macro HeapObjectToFixedDoubleArray(HeapObject): FixedDoubleArray
labels CastError;
extern macro HeapObjectToString(HeapObject): String
labels CastError;
extern macro HeapObjectToHeapNumber(HeapObject): HeapNumber
labels CastError;
extern macro TaggedToNumber(Object): Number
labels CastError;
@ -428,6 +471,10 @@ CastHeapObject<JSArray>(o: HeapObject): JSArray
labels CastError {
return HeapObjectToJSArray(o) otherwise CastError;
}
CastHeapObject<String>(o: HeapObject): String
labels CastError {
return HeapObjectToString(o) otherwise CastError;
}
macro Cast<A: type>(o: HeapObject): A
labels CastError {
@ -475,6 +522,7 @@ extern macro ChangeInt32ToIntPtr(int32): intptr; // Sign-extends.
extern macro ChangeUint32ToWord(uint32): uintptr; // Doesn't sign-extend.
extern macro LoadNativeContext(Context): NativeContext;
extern macro LoadJSArrayElementsMap(constexpr ElementsKind, Context): Map;
extern macro ChangeNonnegativeNumberToUintPtr(Number): uintptr;
extern macro NumberConstant(constexpr float64): Number;
extern macro NumberConstant(constexpr int32): Number;
@ -610,6 +658,9 @@ macro Convert<A: type>(n: Number): A;
Convert<float64>(n: Number): float64 {
return ChangeNumberToFloat64(n);
}
Convert<uintptr>(n: Number): uintptr {
return ChangeNonnegativeNumberToUintPtr(n);
}
macro Convert<A: type>(f: float32): A;
Convert<float64>(f: float32): float64 {
return ChangeFloat32ToFloat64(f);
@ -651,6 +702,7 @@ extern macro UnsafeCastObjectToNumberDictionary(Object): NumberDictionary;
extern macro UnsafeCastObjectToJSReceiver(Object): JSReceiver;
extern macro UnsafeCastObjectToJSObject(Object): JSObject;
extern macro UnsafeCastObjectToMap(Object): Map;
extern macro UnsafeCastObjectToString(Object): String;
macro UnsafeCast<A: type>(n: Number): A;
UnsafeCast<HeapNumber>(n: Number): HeapNumber {
@ -705,6 +757,9 @@ UnsafeCast<FixedArrayBase>(o: Object): FixedArrayBase {
UnsafeCast<Context>(o: Object): Context {
return UnsafeCastObjectToContext(o);
}
UnsafeCast<String>(o: Object): String {
return UnsafeCastObjectToString(o);
}
// RawCasts should *never* be used anywhere in Torque code except for
// in Torque-based UnsafeCast operators preceeded by an appropriate
@ -781,14 +836,20 @@ extern operator '.length_fast' macro LoadFastJSArrayLength(JSArray): Smi;
extern operator '.length=' macro StoreJSArrayLength(JSArray, Smi);
extern operator '.length' macro LoadFixedArrayBaseLength(FixedArrayBase): Smi;
extern operator '.length_intptr' macro LoadAndUntagFixedArrayBaseLength(
FixedArrayBase): intptr;
extern operator '[]' macro LoadFixedArrayElement(FixedArray, intptr): Object;
extern operator '[]' macro LoadFixedArrayElement(FixedArray, Smi): Object;
extern operator '[]' macro LoadFixedArrayElement(
FixedArray, constexpr int31): Object;
extern operator '[]=' macro StoreFixedArrayElement(
FixedArray, intptr, Object): void;
FixedArray, intptr, Smi): void;
extern operator '[]=' macro StoreFixedArrayElement(
FixedArray, constexpr int31, Object): void;
FixedArray, intptr, HeapObject): void;
extern operator '[]=' macro StoreFixedArrayElement(
FixedArray, constexpr int31, Smi): void;
extern operator '[]=' macro StoreFixedArrayElement(
FixedArray, constexpr int31, HeapObject): void;
extern operator '[]=' macro StoreFixedArrayElementSmi(
FixedArray, Smi, Object): void;
operator '[]=' macro StoreFixedDoubleArrayNumber(
@ -806,10 +867,14 @@ extern macro Float64SilenceNaN(float64): float64;
extern macro StoreFixedDoubleArrayElement(
FixedDoubleArray, Object, float64, constexpr ParameterMode);
extern macro StoreFixedArrayElement(
FixedArray, intptr, Object, constexpr WriteBarrierMode): void;
macro StoreFixedDoubleArrayElementWithSmiIndex(
array: FixedDoubleArray, index: Smi, value: float64) {
StoreFixedDoubleArrayElement(array, index, value, SMI_PARAMETERS);
}
extern macro GetNumberDictionaryNumberOfElements(NumberDictionary): Smi;
extern macro BasicLoadNumberDictionaryElement(NumberDictionary, intptr): Object
labels NotData, IfHole;
@ -846,9 +911,11 @@ macro AllowNonNumberElements(kind: ElementsKind): ElementsKind {
extern macro AllocateZeroedFixedArray(intptr): FixedArray;
extern macro AllocateZeroedFixedDoubleArray(intptr): FixedDoubleArray;
extern macro CalculateNewElementsCapacity(Smi): Smi;
extern macro CalculateNewElementsCapacity(intptr): intptr;
extern macro AllocateFixedArrayWithHoles(
intptr, constexpr AllocationFlags): FixedArray;
extern macro CopyFixedArrayElements(
constexpr ElementsKind, FixedArray, constexpr ElementsKind, FixedArray,
intptr, intptr, intptr): void;
@ -858,14 +925,14 @@ extern macro CopyFixedArrayElements(
extern macro AllocateJSArray(constexpr ElementsKind, Map, intptr, Smi): JSArray;
extern macro AllocateJSArray(constexpr ElementsKind, Map, Smi, Smi): JSArray;
extern macro IsElementsKindGreaterThan(
ElementsKind, constexpr ElementsKind): bool;
extern operator '[]=' macro StoreFixedDoubleArrayElementSmi(
FixedDoubleArray, Smi, float64): void;
extern macro LoadDoubleWithHoleCheck(FixedDoubleArray, Smi): float64
labels IfHole;
extern macro LoadDoubleWithHoleCheck(FixedDoubleArray, intptr): float64
labels IfHole;
extern macro StoreFixedDoubleArrayHoleSmi(FixedDoubleArray, Smi): void;
extern macro Call(Context, Callable, Object): Object;
@ -882,6 +949,10 @@ extern macro ExtractFixedArray(
FixedArrayBase, Smi, Smi, Smi,
constexpr ExtractFixedArrayFlags): FixedArrayBase;
extern macro ExtractFixedArray(
FixedArray, intptr, intptr, intptr,
constexpr ExtractFixedArrayFlags): FixedArray;
extern builtin ExtractFastJSArray(Context, JSArray, Smi, Smi): JSArray;
extern macro MoveElements(
@ -1021,3 +1092,14 @@ macro GetLengthProperty(implicit context: Context)(o: Object): Number {
return ToLength_Inline(context, length);
}
}
extern macro NumberToString(Number): String;
extern macro HasOnlyOneByteChars(InstanceType): bool;
extern macro AllocateSeqOneByteString(implicit context: Context)(uint32):
String;
extern macro AllocateSeqTwoByteString(implicit context: Context)(uint32):
String;
extern macro TryIntPtrAdd(intptr, intptr): intptr
labels IfOverflow;
extern builtin ObjectToString(Context, Object): Object;

View File

@ -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_; }

View File

@ -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 */ \

View File

@ -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),

View File

@ -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(),

View File

@ -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,

View File

@ -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) \

View File

@ -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:

View File

@ -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>();
}

View File

@ -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") \

View File

@ -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;
});

View File

@ -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.

View File

@ -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) >>

View File

@ -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());

View File

@ -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)) {

View File

@ -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);

View File

@ -0,0 +1,14 @@
// 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(''));
})();

View File

@ -0,0 +1,152 @@
// 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();

View File

@ -0,0 +1,87 @@
// 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());
})();

View File

@ -0,0 +1,16 @@
// 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());

View File

@ -0,0 +1,29 @@
// 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);
})();

View File

@ -0,0 +1,197 @@
// 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());
})();

View File

@ -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;

View File

@ -0,0 +1,72 @@
// 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());
})();

View File

@ -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

View File

@ -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.