Permalink
Fetching contributors…
Cannot retrieve contributors at this time
13059 lines (11378 sloc) 509 KB
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "RuntimeLibraryPch.h"
#include "Types/PathTypeHandler.h"
#include "Types/SpreadArgument.h"
// TODO: Change this generic fatal error to the descriptive one.
#define AssertAndFailFast(x) if (!(x)) { Assert(x); Js::Throw::FatalInternalError(); }
using namespace Js;
// Make sure EmptySegment points to read-only memory.
// Can't do this the easy way because SparseArraySegment has a constructor...
static const char EmptySegmentData[sizeof(SparseArraySegmentBase)] = {0};
const SparseArraySegmentBase *JavascriptArray::EmptySegment = (SparseArraySegmentBase *)&EmptySegmentData;
// col0 : allocation bucket
// col1 : No. of missing items to set during initialization depending on bucket.
// col2 : allocation size for elements in given bucket.
// col1 and col2 is calculated at runtime
uint JavascriptNativeFloatArray::allocationBuckets[][AllocationBucketsInfoSize] =
{
{ 3, 0, 0 }, // allocate space for 3 elements for array of length 0,1,2,3
{ 5, 0, 0 }, // allocate space for 5 elements for array of length 4,5
{ 8, 0, 0 }, // allocate space for 8 elements for array of length 6,7,8
};
const Var JavascriptArray::MissingItem = (Var)VarMissingItemPattern;
#if defined(TARGET_64)
const Var JavascriptArray::IntMissingItemVar = (Var)(((uint64)IntMissingItemPattern << 32) | (uint32)IntMissingItemPattern);
uint JavascriptNativeIntArray::allocationBuckets[][AllocationBucketsInfoSize] =
{
// See comments above on how to read this
{2, 0, 0},
{6, 0, 0},
{8, 0, 0},
};
uint JavascriptArray::allocationBuckets[][AllocationBucketsInfoSize] =
{
// See comments above on how to read this
{4, 0, 0},
{6, 0, 0},
{8, 0, 0},
};
#else
const Var JavascriptArray::IntMissingItemVar = (Var)IntMissingItemPattern;
uint JavascriptNativeIntArray::allocationBuckets[][AllocationBucketsInfoSize] =
{
// See comments above on how to read this
{ 3, 0, 0 },
{ 7, 0, 0 },
{ 8, 0, 0 },
};
uint JavascriptArray::allocationBuckets[][AllocationBucketsInfoSize] =
{
// See comments above on how to read this
{ 4, 0, 0 },
{ 8, 0, 0 },
};
#endif
const int32 JavascriptNativeIntArray::MissingItem = IntMissingItemPattern;
const double JavascriptNativeFloatArray::MissingItem = *(double*)&FloatMissingItemPattern;
// Allocate enough space for 4 inline property slots and 16 inline element slots
const size_t JavascriptArray::StackAllocationSize = DetermineAllocationSize<JavascriptArray, 4>(16);
const size_t JavascriptNativeIntArray::StackAllocationSize = DetermineAllocationSize<JavascriptNativeIntArray, 4>(16);
const size_t JavascriptNativeFloatArray::StackAllocationSize = DetermineAllocationSize<JavascriptNativeFloatArray, 4>(16);
SegmentBTree::SegmentBTree()
: segmentCount(0),
segments(nullptr),
keys(nullptr),
children(nullptr)
{
}
uint32 SegmentBTree::GetLazyCrossOverLimit()
{
#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
if (Js::Configuration::Global.flags.DisableArrayBTree)
{
return Js::JavascriptArray::InvalidIndex;
}
else if (Js::Configuration::Global.flags.ForceArrayBTree)
{
return ARRAY_CROSSOVER_FOR_VALIDATE;
}
#endif
#ifdef VALIDATE_ARRAY
if (Js::Configuration::Global.flags.ArrayValidate)
{
return ARRAY_CROSSOVER_FOR_VALIDATE;
}
#endif
return SegmentBTree::MinDegree * 3;
}
BOOL SegmentBTree::IsLeaf() const
{
return children == NULL;
}
BOOL SegmentBTree::IsFullNode() const
{
return segmentCount == MaxKeys;
}
void SegmentBTree::InternalFind(SegmentBTree* node, uint32 itemIndex, SparseArraySegmentBase*& prev, SparseArraySegmentBase*& matchOrNext)
{
uint32 i = 0;
for(; i < node->segmentCount; i++)
{
Assert(node->keys[i] == node->segments[i]->left);
if (itemIndex < node->keys[i])
{
break;
}
}
// i indicates the 1st segment in the node past any matching segment.
// the i'th child is the children to the 'left' of the i'th segment.
// If itemIndex matches segment i-1 (note that left is always a match even when length == 0)
bool matches = i > 0 && (itemIndex == node->keys[i-1] || itemIndex < node->keys[i-1] + node->segments[i-1]->length);
if (matches)
{
// Find prev segment
if (node->IsLeaf())
{
if (i > 1)
{
// Previous is either sibling or set in a parent
prev = node->segments[i-2];
}
}
else
{
// prev is the right most leaf in children[i-1] tree
SegmentBTree* child = &node->children[i - 1];
while (!child->IsLeaf())
{
child = &child->children[child->segmentCount];
}
prev = child->segments[child->segmentCount - 1];
}
// Return the matching segment
matchOrNext = node->segments[i-1];
}
else // itemIndex in between segment i-1 and i
{
if (i > 0)
{
// Store in previous in case a match or next is the first segment in a child.
prev = node->segments[i-1];
}
if (node->IsLeaf())
{
matchOrNext = (i == 0 ? node->segments[0] : PointerValue(prev->next));
}
else
{
InternalFind(node->children + i, itemIndex, prev, matchOrNext);
}
}
}
void SegmentBTreeRoot::Find(uint32 itemIndex, SparseArraySegmentBase*& prev, SparseArraySegmentBase*& matchOrNext)
{
prev = matchOrNext = NULL;
InternalFind(this, itemIndex, prev, matchOrNext);
Assert(prev == NULL || (prev->next == matchOrNext));// If prev exists it is immediately before matchOrNext in the list of arraysegments
Assert(prev == NULL || (prev->left < itemIndex && prev->left + prev->length <= itemIndex)); // prev should never be a match (left is a match if length == 0)
Assert(matchOrNext == NULL || (matchOrNext->left >= itemIndex || matchOrNext->left + matchOrNext->length > itemIndex));
}
void SegmentBTreeRoot::Add(Recycler* recycler, SparseArraySegmentBase* newSeg)
{
if (IsFullNode())
{
SegmentBTree * children = AllocatorNewArrayZ(Recycler, recycler, SegmentBTree, MaxDegree);
children[0] = *this;
// Even though the segments point to a GC pointer, the main array should keep a references
// as well. So just make it a leaf allocation
this->segmentCount = 0;
this->segments = AllocatorNewArrayLeafZ(Recycler, recycler, SparseArraySegmentBase*, MaxKeys);
this->keys = AllocatorNewArrayLeafZ(Recycler,recycler,uint32,MaxKeys);
this->children = children;
// This split is the only way the tree gets deeper
SplitChild(recycler, this, 0, &children[0]);
}
InsertNonFullNode(recycler, this, newSeg);
}
void SegmentBTree::SwapSegment(uint32 originalKey, SparseArraySegmentBase* oldSeg, SparseArraySegmentBase* newSeg)
{
// Find old segment
uint32 itemIndex = originalKey;
uint32 i = 0;
for(; i < segmentCount; i++)
{
Assert(keys[i] == segments[i]->left || (oldSeg == newSeg && newSeg == segments[i]));
if (itemIndex < keys[i])
{
break;
}
}
// i is 1 past any match
if (i > 0)
{
if (oldSeg == segments[i-1])
{
segments[i-1] = newSeg;
keys[i-1] = newSeg->left;
return;
}
}
Assert(!IsLeaf());
children[i].SwapSegment(originalKey, oldSeg, newSeg);
}
void SegmentBTree::SplitChild(Recycler* recycler, SegmentBTree* parent, uint32 iChild, SegmentBTree* child)
{
// Split child in two, move it's median key up to parent, and put the result of the split
// on either side of the key moved up into parent
Assert(child != NULL);
Assert(parent != NULL);
Assert(!parent->IsFullNode());
Assert(child->IsFullNode());
SegmentBTree newNode;
newNode.segmentCount = MinKeys;
// Even though the segments point to a GC pointer, the main array should keep a references
// as well. So just make it a leaf allocation
newNode.segments = AllocatorNewArrayLeafZ(Recycler, recycler, SparseArraySegmentBase*, MaxKeys);
newNode.keys = AllocatorNewArrayLeafZ(Recycler,recycler,uint32,MaxKeys);
// Move the keys above the median into the new node
for(uint32 i = 0; i < MinKeys; i++)
{
newNode.segments[i] = child->segments[i+MinDegree];
newNode.keys[i] = child->keys[i+MinDegree];
// Do not leave false positive references around in the b-tree
child->segments[i+MinDegree] = nullptr;
}
// If children exist move those as well.
if (!child->IsLeaf())
{
newNode.children = AllocatorNewArrayZ(Recycler, recycler, SegmentBTree, MaxDegree);
for(uint32 j = 0; j < MinDegree; j++)
{
newNode.children[j] = child->children[j+MinDegree];
// Do not leave false positive references around in the b-tree
child->children[j+MinDegree].segments = nullptr;
child->children[j+MinDegree].children = nullptr;
}
}
child->segmentCount = MinKeys;
// Make room for the new child in parent
for(uint32 j = parent->segmentCount; j > iChild; j--)
{
parent->children[j+1] = parent->children[j];
}
// Copy the contents of the new node into the correct place in the parent's child array
parent->children[iChild+1] = newNode;
// Move the keys to make room for the median key
for(uint32 k = parent->segmentCount; k > iChild; k--)
{
parent->segments[k] = parent->segments[k-1];
parent->keys[k] = parent->keys[k-1];
}
// Move the median key into the proper place in the parent node
parent->segments[iChild] = child->segments[MinKeys];
parent->keys[iChild] = child->keys[MinKeys];
// Do not leave false positive references around in the b-tree
child->segments[MinKeys] = nullptr;
parent->segmentCount++;
}
void SegmentBTree::InsertNonFullNode(Recycler* recycler, SegmentBTree* node, SparseArraySegmentBase* newSeg)
{
Assert(!node->IsFullNode());
AnalysisAssert(node->segmentCount < MaxKeys); // Same as !node->IsFullNode()
Assert(newSeg != NULL);
if (node->IsLeaf())
{
// Move the keys
uint32 i = node->segmentCount - 1;
while( (i != -1) && (newSeg->left < node->keys[i]))
{
node->segments[i+1] = node->segments[i];
node->keys[i+1] = node->keys[i];
i--;
}
if (!node->segments)
{
// Even though the segments point to a GC pointer, the main array should keep a references
// as well. So just make it a leaf allocation
node->segments = AllocatorNewArrayLeafZ(Recycler, recycler, SparseArraySegmentBase*, MaxKeys);
node->keys = AllocatorNewArrayLeafZ(Recycler, recycler, uint32, MaxKeys);
}
node->segments[i + 1] = newSeg;
node->keys[i + 1] = newSeg->left;
node->segmentCount++;
}
else
{
// find the correct child node
uint32 i = node->segmentCount-1;
while((i != -1) && (newSeg->left < node->keys[i]))
{
i--;
}
i++;
// Make room if full
if(node->children[i].IsFullNode())
{
// This split doesn't make the tree any deeper as node already has children.
SplitChild(recycler, node, i, node->children+i);
Assert(node->keys[i] == node->segments[i]->left);
if (newSeg->left > node->keys[i])
{
i++;
}
}
InsertNonFullNode(recycler, node->children+i, newSeg);
}
}
inline void ThrowTypeErrorOnFailureHelper::ThrowTypeErrorOnFailure(BOOL operationSucceeded)
{
if (IsThrowTypeError(operationSucceeded))
{
ThrowTypeErrorOnFailure();
}
}
inline void ThrowTypeErrorOnFailureHelper::ThrowTypeErrorOnFailure()
{
JavascriptError::ThrowTypeError(m_scriptContext, VBSERR_ActionNotSupported, m_functionName);
}
inline BOOL ThrowTypeErrorOnFailureHelper::IsThrowTypeError(BOOL operationSucceeded)
{
return !operationSucceeded;
}
// Make sure EmptySegment points to read-only memory.
// Can't do this the easy way because SparseArraySegment has a constructor...
JavascriptArray::JavascriptArray(DynamicType * type)
: ArrayObject(type, false, 0)
{
Assert(type->GetTypeId() == TypeIds_Array || type->GetTypeId() == TypeIds_NativeIntArray || type->GetTypeId() == TypeIds_NativeFloatArray || ((type->GetTypeId() == TypeIds_ES5Array || type->GetTypeId() == TypeIds_Object) && type->GetPrototype() == GetScriptContext()->GetLibrary()->GetArrayPrototype()));
Assert(EmptySegment->length == 0 && EmptySegment->size == 0 && EmptySegment->next == NULL);
InitArrayFlags(DynamicObjectFlags::InitialArrayValue);
SetHeadAndLastUsedSegment(const_cast<SparseArraySegmentBase *>(EmptySegment));
}
JavascriptArray::JavascriptArray(uint32 length, DynamicType * type)
: ArrayObject(type, false, length)
{
Assert(JavascriptArray::IsNonES5Array(type->GetTypeId()));
Assert(EmptySegment->length == 0 && EmptySegment->size == 0 && EmptySegment->next == NULL);
InitArrayFlags(DynamicObjectFlags::InitialArrayValue);
SetHeadAndLastUsedSegment(const_cast<SparseArraySegmentBase *>(EmptySegment));
}
JavascriptArray::JavascriptArray(uint32 length, uint32 size, DynamicType * type)
: ArrayObject(type, false, length)
{
Assert(type->GetTypeId() == TypeIds_Array);
InitArrayFlags(DynamicObjectFlags::InitialArrayValue);
Recycler* recycler = GetRecycler();
SetHeadAndLastUsedSegment(SparseArraySegment<Var>::AllocateSegment(recycler, 0, 0, size, nullptr));
}
JavascriptArray::JavascriptArray(DynamicType * type, uint32 size)
: ArrayObject(type, false)
{
InitArrayFlags(DynamicObjectFlags::InitialArrayValue);
SetHeadAndLastUsedSegment(DetermineInlineHeadSegmentPointer<JavascriptArray, 0, false>(this));
head->size = size;
head->CheckLengthvsSize();
Var fill = Js::JavascriptArray::MissingItem;
for (uint i = 0; i < size; i++)
{
SparseArraySegment<Var>::From(head)->elements[i] = fill;
}
}
JavascriptNativeIntArray::JavascriptNativeIntArray(uint32 length, uint32 size, DynamicType * type)
: JavascriptNativeArray(type)
{
Assert(type->GetTypeId() == TypeIds_NativeIntArray);
this->length = length;
Recycler* recycler = GetRecycler();
SetHeadAndLastUsedSegment(SparseArraySegment<int32>::AllocateSegment(recycler, 0, 0, size, nullptr));
}
JavascriptNativeIntArray::JavascriptNativeIntArray(DynamicType * type, uint32 size)
: JavascriptNativeArray(type)
{
SetHeadAndLastUsedSegment(DetermineInlineHeadSegmentPointer<JavascriptNativeIntArray, 0, false>(this));
head->size = size;
head->CheckLengthvsSize();
SparseArraySegment<int32>::From(head)->FillSegmentBuffer(0, size);
}
JavascriptNativeFloatArray::JavascriptNativeFloatArray(uint32 length, uint32 size, DynamicType * type)
: JavascriptNativeArray(type)
{
Assert(type->GetTypeId() == TypeIds_NativeFloatArray);
this->length = length;
Recycler* recycler = GetRecycler();
SetHeadAndLastUsedSegment(SparseArraySegment<double>::AllocateSegment(recycler, 0, 0, size, nullptr));
}
JavascriptNativeFloatArray::JavascriptNativeFloatArray(DynamicType * type, uint32 size)
: JavascriptNativeArray(type)
{
SetHeadAndLastUsedSegment(DetermineInlineHeadSegmentPointer<JavascriptNativeFloatArray, 0, false>(this));
head->size = size;
head->CheckLengthvsSize();
SparseArraySegment<double>::From(head)->FillSegmentBuffer(0, size);
}
bool JavascriptArray::IsNonES5Array(Var aValue)
{
TypeId typeId = JavascriptOperators::GetTypeId(aValue);
return JavascriptArray::IsNonES5Array(typeId);
}
bool JavascriptArray::IsNonES5Array(TypeId typeId)
{
return typeId >= TypeIds_ArrayFirst && typeId <= TypeIds_ArrayLast;
}
JavascriptArray* JavascriptArray::TryVarToNonES5Array(Var aValue)
{
return JavascriptArray::IsNonES5Array(aValue) ? UnsafeVarTo<JavascriptArray>(aValue) : nullptr;
}
bool JavascriptArray::IsVarArray(Var aValue)
{
TypeId typeId = JavascriptOperators::GetTypeId(aValue);
return JavascriptArray::IsVarArray(typeId);
}
bool JavascriptArray::IsVarArray(TypeId typeId)
{
return typeId == TypeIds_Array;
}
template<typename T>
bool JavascriptArray::IsMissingItemAt(uint32 index) const
{
SparseArraySegment<T>* headSeg = SparseArraySegment<T>::From(this->head);
return SparseArraySegment<T>::IsMissingItem(&headSeg->elements[index]);
}
bool JavascriptArray::IsMissingItem(uint32 index)
{
if (!(this->head->left <= index && index < (this->head->left+ this->head->length)))
{
return false;
}
bool isIntArray = false, isFloatArray = false;
this->GetArrayTypeAndConvert(&isIntArray, &isFloatArray);
if (isIntArray)
{
return IsMissingItemAt<int32>(index);
}
else if (isFloatArray)
{
return IsMissingItemAt<double>(index);
}
else
{
return IsMissingItemAt<Var>(index);
}
}
// Get JavascriptArray* from a Var, which is either a JavascriptArray* or ESArray*.
JavascriptArray* JavascriptArray::FromAnyArray(Var aValue)
{
AssertOrFailFastMsg(VarIs<JavascriptArray>(aValue), "Ensure var is actually a 'JavascriptArray' or 'ES5Array'");
return static_cast<JavascriptArray *>(VarTo<RecyclableObject>(aValue));
}
JavascriptArray* JavascriptArray::UnsafeFromAnyArray(Var aValue)
{
AssertMsg(VarIs<JavascriptArray>(aValue), "Ensure var is actually a 'JavascriptArray' or 'ES5Array'");
return static_cast<JavascriptArray *>(UnsafeVarTo<RecyclableObject>(aValue));
}
// Check if a Var is a direct-accessible (fast path) JavascriptArray.
bool JavascriptArray::IsDirectAccessArray(Var aValue)
{
return VarIs<RecyclableObject>(aValue) &&
(VirtualTableInfo<JavascriptArray>::HasVirtualTable(aValue) ||
VirtualTableInfo<JavascriptNativeIntArray>::HasVirtualTable(aValue) ||
VirtualTableInfo<JavascriptNativeFloatArray>::HasVirtualTable(aValue));
}
bool JavascriptArray::IsInlineSegment(SparseArraySegmentBase *seg, JavascriptArray *pArr)
{
if (seg == nullptr)
{
return false;
}
SparseArraySegmentBase* inlineHeadSegment = nullptr;
if (VarIs<JavascriptNativeArray>(pArr))
{
if (VarIs<JavascriptNativeFloatArray>(pArr))
{
inlineHeadSegment = DetermineInlineHeadSegmentPointer<JavascriptNativeFloatArray, 0, true>((JavascriptNativeFloatArray*)pArr);
}
else
{
AssertOrFailFast(VarIs<JavascriptNativeIntArray>(pArr));
inlineHeadSegment = DetermineInlineHeadSegmentPointer<JavascriptNativeIntArray, 0, true>((JavascriptNativeIntArray*)pArr);
}
Assert(inlineHeadSegment);
return (seg == inlineHeadSegment);
}
// This will result in false positives. It is used because DetermineInlineHeadSegmentPointer
// does not handle Arrays that change type e.g. from JavascriptNativeIntArray to JavascriptArray
// This conversion in particular is problematic because JavascriptNativeIntArray is larger than JavascriptArray
// so the returned head segment ptr never equals pArr->head. So we will default to using this and deal with
// false positives. It is better than always doing a hard copy.
return pArr->head != nullptr && HasInlineHeadSegment(pArr->head->length);
}
DynamicObjectFlags JavascriptArray::GetFlags() const
{
return GetArrayFlags();
}
DynamicObjectFlags JavascriptArray::GetFlags_Unchecked() const // do not use except in extreme circumstances
{
return GetArrayFlags_Unchecked();
}
void JavascriptArray::SetFlags(const DynamicObjectFlags flags)
{
SetArrayFlags(flags);
}
DynamicType * JavascriptArray::GetInitialType(ScriptContext * scriptContext)
{
return scriptContext->GetLibrary()->GetArrayType();
}
JavascriptArray *JavascriptArray::Jit_GetArrayForArrayOrObjectWithArray(const Var var)
{
bool isObjectWithArray;
return Jit_GetArrayForArrayOrObjectWithArray(var, &isObjectWithArray);
}
JavascriptArray *JavascriptArray::Jit_GetArrayForArrayOrObjectWithArray(const Var var, bool *const isObjectWithArrayRef)
{
Assert(var);
Assert(isObjectWithArrayRef);
*isObjectWithArrayRef = false;
if (!VarIs<RecyclableObject>(var))
{
return nullptr;
}
JavascriptArray *array = nullptr;
INT_PTR vtable = VirtualTableInfoBase::GetVirtualTable(var);
if (!Jit_TryGetArrayForObjectWithArray(var, isObjectWithArrayRef, &vtable, &array))
{
return nullptr;
}
if (vtable != VirtualTableInfo<JavascriptArray>::Address &&
vtable != VirtualTableInfo<CrossSiteObject<JavascriptArray>>::Address &&
vtable != VirtualTableInfo<JavascriptNativeIntArray>::Address &&
vtable != VirtualTableInfo<CrossSiteObject<JavascriptNativeIntArray>>::Address &&
vtable != VirtualTableInfo<JavascriptNativeFloatArray>::Address &&
vtable != VirtualTableInfo<CrossSiteObject<JavascriptNativeFloatArray>>::Address)
{
return nullptr;
}
if (!array)
{
array = VarTo<JavascriptArray>(var);
}
return array;
}
bool JavascriptArray::Jit_TryGetArrayForObjectWithArray(const Var var, bool *const isObjectWithArrayRef, INT_PTR* pVTable, JavascriptArray** pArray)
{
Assert(isObjectWithArrayRef);
Assert(pVTable);
Assert(pArray);
if (*pVTable == VirtualTableInfo<DynamicObject>::Address ||
*pVTable == VirtualTableInfo<CrossSiteObject<DynamicObject>>::Address)
{
ArrayObject* objectArray = VarTo<DynamicObject>(var)->GetObjectArray();
*pArray = (objectArray && VarIs<JavascriptArray>(objectArray)) ? VarTo<JavascriptArray>(objectArray) : nullptr;
if (!(*pArray))
{
return false;
}
*isObjectWithArrayRef = true;
*pVTable = VirtualTableInfoBase::GetVirtualTable(*pArray);
}
return true;
}
JavascriptArray *JavascriptArray::GetArrayForArrayOrObjectWithArray(
const Var var,
bool *const isObjectWithArrayRef,
TypeId *const arrayTypeIdRef)
{
// This is a helper function used by jitted code. The array checks done here match the array checks done by jitted code
// (see Lowerer::GenerateArrayTest) to minimize bailouts.
Assert(var);
Assert(isObjectWithArrayRef);
Assert(arrayTypeIdRef);
*isObjectWithArrayRef = false;
*arrayTypeIdRef = TypeIds_Undefined;
if(!VarIs<RecyclableObject>(var))
{
return nullptr;
}
JavascriptArray *array = nullptr;
INT_PTR vtable = VirtualTableInfoBase::GetVirtualTable(var);
if(vtable == VirtualTableInfo<DynamicObject>::Address)
{
ArrayObject* objectArray = VarTo<DynamicObject>(var)->GetObjectArray();
array = (objectArray && IsNonES5Array(objectArray)) ? VarTo<JavascriptArray>(objectArray) : nullptr;
if(!array)
{
return nullptr;
}
*isObjectWithArrayRef = true;
vtable = VirtualTableInfoBase::GetVirtualTable(array);
}
if(vtable == VirtualTableInfo<JavascriptArray>::Address)
{
*arrayTypeIdRef = TypeIds_Array;
}
else if(vtable == VirtualTableInfo<JavascriptNativeIntArray>::Address)
{
*arrayTypeIdRef = TypeIds_NativeIntArray;
}
else if(vtable == VirtualTableInfo<JavascriptNativeFloatArray>::Address)
{
*arrayTypeIdRef = TypeIds_NativeFloatArray;
}
else
{
return nullptr;
}
if(!array)
{
array = VarTo<JavascriptArray>(var);
}
return array;
}
const SparseArraySegmentBase *JavascriptArray::Jit_GetArrayHeadSegmentForArrayOrObjectWithArray(const Var var)
{
JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(Array_Jit_GetArrayHeadSegmentForArrayOrObjectWithArray);
JavascriptArray *const array = Jit_GetArrayForArrayOrObjectWithArray(var);
return array ? array->head : nullptr;
JIT_HELPER_END(Array_Jit_GetArrayHeadSegmentForArrayOrObjectWithArray);
}
uint32 JavascriptArray::Jit_GetArrayHeadSegmentLength(const SparseArraySegmentBase *const headSegment)
{
JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(Array_Jit_GetArrayHeadSegmentLength);
return headSegment ? headSegment->length : 0;
JIT_HELPER_END(Array_Jit_GetArrayHeadSegmentLength);
}
bool JavascriptArray::Jit_OperationInvalidatedArrayHeadSegment(
const SparseArraySegmentBase *const headSegmentBeforeOperation,
const uint32 headSegmentLengthBeforeOperation,
const Var varAfterOperation)
{
JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(Array_Jit_OperationInvalidatedArrayHeadSegment);
Assert(varAfterOperation);
if(!headSegmentBeforeOperation)
{
return false;
}
const SparseArraySegmentBase *const headSegmentAfterOperation =
Jit_GetArrayHeadSegmentForArrayOrObjectWithArray(varAfterOperation);
return
headSegmentAfterOperation != headSegmentBeforeOperation ||
headSegmentAfterOperation->length != headSegmentLengthBeforeOperation;
JIT_HELPER_END(Array_Jit_OperationInvalidatedArrayHeadSegment);
}
uint32 JavascriptArray::Jit_GetArrayLength(const Var var)
{
JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(Array_Jit_GetArrayLength);
bool isObjectWithArray;
JavascriptArray *const array = Jit_GetArrayForArrayOrObjectWithArray(var, &isObjectWithArray);
return array && !isObjectWithArray ? array->GetLength() : 0;
JIT_HELPER_END(Array_Jit_GetArrayLength);
}
bool JavascriptArray::Jit_OperationInvalidatedArrayLength(const uint32 lengthBeforeOperation, const Var varAfterOperation)
{
JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(Array_Jit_OperationInvalidatedArrayLength);
return Jit_GetArrayLength(varAfterOperation) != lengthBeforeOperation;
JIT_HELPER_END(Array_Jit_OperationInvalidatedArrayLength);
}
DynamicObjectFlags JavascriptArray::Jit_GetArrayFlagsForArrayOrObjectWithArray(const Var var)
{
JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(Array_Jit_GetArrayFlagsForArrayOrObjectWithArray);
JavascriptArray *const array = Jit_GetArrayForArrayOrObjectWithArray(var);
return array && array->UsesObjectArrayOrFlagsAsFlags() ? array->GetFlags() : DynamicObjectFlags::None;
JIT_HELPER_END(Array_Jit_GetArrayFlagsForArrayOrObjectWithArray);
}
bool JavascriptArray::Jit_OperationCreatedFirstMissingValue(
const DynamicObjectFlags flagsBeforeOperation,
const Var varAfterOperation)
{
JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(Array_Jit_OperationCreatedFirstMissingValue);
Assert(varAfterOperation);
return
!!(flagsBeforeOperation & DynamicObjectFlags::HasNoMissingValues) &&
!(Jit_GetArrayFlagsForArrayOrObjectWithArray(varAfterOperation) & DynamicObjectFlags::HasNoMissingValues);
JIT_HELPER_END(Array_Jit_OperationCreatedFirstMissingValue);
}
bool JavascriptArray::HasNoMissingValues() const
{
return !!(GetFlags() & DynamicObjectFlags::HasNoMissingValues);
}
bool JavascriptArray::HasNoMissingValues_Unchecked() const // do not use except in extreme circumstances
{
return !!(GetFlags_Unchecked() & DynamicObjectFlags::HasNoMissingValues);
}
void JavascriptArray::SetHasNoMissingValues(const bool hasNoMissingValues)
{
SetFlags(
hasNoMissingValues
? GetFlags() | DynamicObjectFlags::HasNoMissingValues
: GetFlags() & ~DynamicObjectFlags::HasNoMissingValues);
}
template<class T>
bool JavascriptArray::IsMissingHeadSegmentItemImpl(const uint32 index) const
{
Assert(index < head->length);
return SparseArraySegment<T>::IsMissingItem(&SparseArraySegment<T>::From(head)->elements[index]);
}
bool JavascriptArray::IsMissingHeadSegmentItem(const uint32 index) const
{
return IsMissingHeadSegmentItemImpl<Var>(index);
}
#if ENABLE_COPYONACCESS_ARRAY
void JavascriptCopyOnAccessNativeIntArray::ConvertCopyOnAccessSegment()
{
Assert(this->GetScriptContext()->GetLibrary()->cacheForCopyOnAccessArraySegments->IsValidIndex(::Math::PointerCastToIntegral<uint32>(this->GetHead())));
SparseArraySegment<int32> *seg = this->GetScriptContext()->GetLibrary()->cacheForCopyOnAccessArraySegments->GetSegmentByIndex(::Math::PointerCastToIntegral<byte>(this->GetHead()));
SparseArraySegment<int32> *newSeg = SparseArraySegment<int32>::AllocateLiteralHeadSegment(this->GetRecycler(), seg->length);
#if ENABLE_DEBUG_CONFIG_OPTIONS
if (Js::Configuration::Global.flags.TestTrace.IsEnabled(Js::CopyOnAccessArrayPhase))
{
Output::Print(_u("Convert copy-on-access array: index(%d) length(%d)\n"), this->GetHead(), seg->length);
Output::Flush();
}
#endif
newSeg->CopySegment(this->GetRecycler(), newSeg, 0, seg, 0, seg->length);
this->SetHeadAndLastUsedSegment(newSeg);
VirtualTableInfo<JavascriptNativeIntArray>::SetVirtualTable(this);
this->type = JavascriptNativeIntArray::GetInitialType(this->GetScriptContext());
ArrayCallSiteInfo *arrayInfo = this->GetArrayCallSiteInfo();
if (arrayInfo && !arrayInfo->isNotCopyOnAccessArray)
{
arrayInfo->isNotCopyOnAccessArray = 1;
}
}
uint32 JavascriptCopyOnAccessNativeIntArray::GetNextIndex(uint32 index) const
{
if (this->length == 0 || (index != Js::JavascriptArray::InvalidIndex && index >= this->length))
{
return Js::JavascriptArray::InvalidIndex;
}
else if (index == Js::JavascriptArray::InvalidIndex)
{
return 0;
}
else
{
return index + 1;
}
}
BOOL JavascriptCopyOnAccessNativeIntArray::DirectGetItemAt(uint32 index, int* outVal)
{
Assert(this->GetScriptContext()->GetLibrary()->cacheForCopyOnAccessArraySegments->IsValidIndex(::Math::PointerCastToIntegral<uint32>(this->GetHead())));
SparseArraySegment<int32> *seg = this->GetScriptContext()->GetLibrary()->cacheForCopyOnAccessArraySegments->GetSegmentByIndex(::Math::PointerCastToIntegral<byte>(this->GetHead()));
if (this->length == 0 || index == Js::JavascriptArray::InvalidIndex || index >= this->length)
{
return FALSE;
}
else
{
*outVal = seg->elements[index];
return TRUE;
}
}
#endif
bool JavascriptNativeIntArray::IsMissingHeadSegmentItem(const uint32 index) const
{
return IsMissingHeadSegmentItemImpl<int32>(index);
}
bool JavascriptNativeFloatArray::IsMissingHeadSegmentItem(const uint32 index) const
{
return IsMissingHeadSegmentItemImpl<double>(index);
}
void JavascriptArray::InternalFillFromPrototype(JavascriptArray *dstArray, uint32 dstIndex, JavascriptArray *srcArray, uint32 start, uint32 end, uint32 count)
{
RecyclableObject* prototype = srcArray->GetPrototype();
while (start + count != end && !JavascriptOperators::IsNull(prototype))
{
ForEachOwnMissingArrayIndexOfObject(srcArray, dstArray, prototype, start, end, dstIndex, [&](uint32 index, Var value) {
uint32 n = dstIndex + (index - start);
dstArray->SetItem(n, value, PropertyOperation_None);
count++;
});
prototype = prototype->GetPrototype();
}
}
/* static */
bool JavascriptArray::HasInlineHeadSegment(uint32 length)
{
return length <= SparseArraySegmentBase::INLINE_CHUNK_SIZE;
}
Var JavascriptArray::OP_NewScArray(uint32 elementCount, ScriptContext* scriptContext)
{
JIT_HELPER_NOT_REENTRANT_HEADER(ScrArr_OP_NewScArray, reentrancylock, scriptContext->GetThreadContext());
// Called only to create array literals: size is known.
return scriptContext->GetLibrary()->CreateArrayLiteral(elementCount);
JIT_HELPER_END(ScrArr_OP_NewScArray);
}
Var JavascriptArray::OP_NewScArrayWithElements(uint32 elementCount, Var *elements, ScriptContext* scriptContext)
{
JIT_HELPER_NOT_REENTRANT_HEADER(ScrArr_OP_NewScArrayWithElements, reentrancylock, scriptContext->GetThreadContext());
// Called only to create array literals: size is known.
JavascriptArray *arr = scriptContext->GetLibrary()->CreateArrayLiteral(elementCount);
SparseArraySegment<Var> *head = SparseArraySegment<Var>::From(arr->head);
Assert(elementCount <= head->length);
CopyArray(head->elements, head->length, elements, elementCount);
#ifdef VALIDATE_ARRAY
arr->ValidateArray();
#endif
return arr;
JIT_HELPER_END(ScrArr_OP_NewScArrayWithElements);
}
Var JavascriptArray::OP_NewScArrayWithMissingValues(uint32 elementCount, ScriptContext* scriptContext)
{
JIT_HELPER_NOT_REENTRANT_HEADER(ScrArr_OP_NewScArrayWithMissingValues, reentrancylock, scriptContext->GetThreadContext());
// Called only to create array literals: size is known.
JavascriptArray *const array = static_cast<JavascriptArray *>(OP_NewScArray(elementCount, scriptContext));
array->SetHasNoMissingValues(false);
SparseArraySegment<Var> *head = SparseArraySegment<Var>::From(array->head);
head->FillSegmentBuffer(0, elementCount);
return array;
JIT_HELPER_END(ScrArr_OP_NewScArrayWithMissingValues);
}
#if ENABLE_PROFILE_INFO
Var JavascriptArray::ProfiledNewScArray(uint32 elementCount, ScriptContext *scriptContext, ArrayCallSiteInfo *arrayInfo, RecyclerWeakReference<FunctionBody> *weakFuncRef)
{
JIT_HELPER_NOT_REENTRANT_HEADER(ScrArr_ProfiledNewScArray, reentrancylock, scriptContext->GetThreadContext());
if (arrayInfo->IsNativeIntArray())
{
JavascriptNativeIntArray *arr = scriptContext->GetLibrary()->CreateNativeIntArrayLiteral(elementCount);
arr->SetArrayProfileInfo(weakFuncRef, arrayInfo);
return arr;
}
if (arrayInfo->IsNativeFloatArray())
{
JavascriptNativeFloatArray *arr = scriptContext->GetLibrary()->CreateNativeFloatArrayLiteral(elementCount);
arr->SetArrayProfileInfo(weakFuncRef, arrayInfo);
return arr;
}
JavascriptArray *arr = scriptContext->GetLibrary()->CreateArrayLiteral(elementCount);
return arr;
JIT_HELPER_END(ScrArr_ProfiledNewScArray);
}
#endif
Var JavascriptArray::OP_NewScIntArray(AuxArray<int32> *ints, ScriptContext* scriptContext)
{
JIT_HELPER_NOT_REENTRANT_HEADER(ScrArr_OP_NewScIntArray, reentrancylock, scriptContext->GetThreadContext());
uint32 count = ints->count;
JavascriptArray *arr = scriptContext->GetLibrary()->CreateArrayLiteral(count);
SparseArraySegment<Var> *head = SparseArraySegment<Var>::From(arr->head);
Assert(count > 0 && count == head->length);
for (uint i = 0; i < count; i++)
{
head->elements[i] = JavascriptNumber::ToVar(ints->elements[i], scriptContext);
}
return arr;
JIT_HELPER_END(ScrArr_OP_NewScIntArray);
}
#if ENABLE_PROFILE_INFO
Var JavascriptArray::ProfiledNewScIntArray(AuxArray<int32> *ints, ScriptContext* scriptContext, ArrayCallSiteInfo *arrayInfo, RecyclerWeakReference<FunctionBody> *weakFuncRef)
{
JIT_HELPER_NOT_REENTRANT_HEADER(ScrArr_ProfiledNewScIntArray, reentrancylock, scriptContext->GetThreadContext());
// Called only to create array literals: size is known.
uint32 count = ints->count;
if (arrayInfo->IsNativeIntArray())
{
JavascriptNativeIntArray *arr;
#if ENABLE_COPYONACCESS_ARRAY
JavascriptLibrary *lib = scriptContext->GetLibrary();
FunctionBody *functionBody = weakFuncRef->Get();
if (JavascriptLibrary::IsCopyOnAccessArrayCallSite(lib, arrayInfo, count))
{
Assert(lib->cacheForCopyOnAccessArraySegments);
arr = scriptContext->GetLibrary()->CreateCopyOnAccessNativeIntArrayLiteral(arrayInfo, functionBody, ints);
}
else
#endif
{
arr = scriptContext->GetLibrary()->CreateNativeIntArrayLiteral(count);
SparseArraySegment<int32> *head = SparseArraySegment<int32>::From(arr->head);
Assert(count > 0 && count == head->length);
CopyArray(head->elements, head->length, ints->elements, count);
}
arr->SetArrayProfileInfo(weakFuncRef, arrayInfo);
return arr;
}
if (arrayInfo->IsNativeFloatArray())
{
JavascriptNativeFloatArray *arr = scriptContext->GetLibrary()->CreateNativeFloatArrayLiteral(count);
SparseArraySegment<double> *head = SparseArraySegment<double>::From(arr->head);
Assert(count > 0 && count == head->length);
for (uint i = 0; i < count; i++)
{
head->elements[i] = (double)ints->elements[i];
}
arr->SetArrayProfileInfo(weakFuncRef, arrayInfo);
return arr;
}
return OP_NewScIntArray(ints, scriptContext);
JIT_HELPER_END(ScrArr_ProfiledNewScIntArray);
}
#endif
Var JavascriptArray::OP_NewScFltArray(AuxArray<double> *doubles, ScriptContext* scriptContext)
{
JIT_HELPER_NOT_REENTRANT_HEADER(ScrArr_OP_NewScFltArray, reentrancylock, scriptContext->GetThreadContext());
uint32 count = doubles->count;
JavascriptArray *arr = scriptContext->GetLibrary()->CreateArrayLiteral(count);
SparseArraySegment<Var> *head = SparseArraySegment<Var>::From(arr->head);
Assert(count > 0 && count == head->length);
for (uint i = 0; i < count; i++)
{
double dval = doubles->elements[i];
int32 ival;
if (JavascriptNumber::TryGetInt32Value(dval, &ival) && !TaggedInt::IsOverflow(ival))
{
head->elements[i] = TaggedInt::ToVarUnchecked(ival);
}
else
{
head->elements[i] = JavascriptNumber::ToVarNoCheck(dval, scriptContext);
}
}
return arr;
JIT_HELPER_END(ScrArr_OP_NewScFltArray);
}
#if ENABLE_PROFILE_INFO
Var JavascriptArray::ProfiledNewScFltArray(AuxArray<double> *doubles, ScriptContext* scriptContext, ArrayCallSiteInfo *arrayInfo, RecyclerWeakReference<FunctionBody> *weakFuncRef)
{
JIT_HELPER_NOT_REENTRANT_HEADER(ScrArr_ProfiledNewScFltArray, reentrancylock, scriptContext->GetThreadContext());
// Called only to create array literals: size is known.
if (arrayInfo->IsNativeFloatArray())
{
arrayInfo->SetIsNotNativeIntArray();
uint32 count = doubles->count;
JavascriptNativeFloatArray *arr = scriptContext->GetLibrary()->CreateNativeFloatArrayLiteral(count);
SparseArraySegment<double> *head = SparseArraySegment<double>::From(arr->head);
Assert(count > 0 && count == head->length);
CopyArray(head->elements, head->length, doubles->elements, count);
arr->SetArrayProfileInfo(weakFuncRef, arrayInfo);
return arr;
}
return OP_NewScFltArray(doubles, scriptContext);
JIT_HELPER_END(ScrArr_ProfiledNewScFltArray);
}
Var JavascriptArray::ProfiledNewInstance(RecyclableObject* function, CallInfo callInfo, ...)
{
JIT_HELPER_REENTRANT_HEADER(ScrArr_ProfiledNewInstance);
ARGUMENTS(args, callInfo);
Assert(VarIs<JavascriptFunction>(function) &&
VarTo<JavascriptFunction>(function)->GetFunctionInfo() == &JavascriptArray::EntryInfo::NewInstance);
Assert(callInfo.Count >= 2);
ArrayCallSiteInfo *arrayInfo = (ArrayCallSiteInfo*)args[0];
JavascriptArray* pNew = nullptr;
if (callInfo.Count == 2)
{
// Exactly one argument, which is the array length if it's a uint32.
Var firstArgument = args[1];
int elementCount;
if (TaggedInt::Is(firstArgument))
{
elementCount = TaggedInt::ToInt32(firstArgument);
if (elementCount < 0)
{
JavascriptError::ThrowRangeError(function->GetScriptContext(), JSERR_ArrayLengthConstructIncorrect);
}
if (arrayInfo && arrayInfo->IsNativeArray())
{
if (arrayInfo->IsNativeIntArray())
{
pNew = function->GetLibrary()->CreateNativeIntArray(elementCount);
}
else
{
pNew = function->GetLibrary()->CreateNativeFloatArray(elementCount);
}
}
else
{
pNew = function->GetLibrary()->CreateArray(elementCount);
}
}
else if (JavascriptNumber::Is_NoTaggedIntCheck(firstArgument))
{
// Non-tagged-int number: make sure the double value is really a uint32.
double value = JavascriptNumber::GetValue(firstArgument);
uint32 uvalue = JavascriptConversion::ToUInt32(value);
if (value != uvalue)
{
JavascriptError::ThrowRangeError(function->GetScriptContext(), JSERR_ArrayLengthConstructIncorrect);
}
if (arrayInfo && arrayInfo->IsNativeArray())
{
if (arrayInfo->IsNativeIntArray())
{
pNew = function->GetLibrary()->CreateNativeIntArray(uvalue);
}
else
{
pNew = function->GetLibrary()->CreateNativeFloatArray(uvalue);
}
}
else
{
pNew = function->GetLibrary()->CreateArray(uvalue);
}
}
else
{
//
// First element is not int/double
// create an array of length 1.
// Set first element as the passed Var
//
pNew = function->GetLibrary()->CreateArray(1);
pNew->DirectSetItemAt<Var>(0, firstArgument);
}
}
else
{
// Called with a list of initial element values.
// Create an array of the appropriate length and walk the list.
if (arrayInfo && arrayInfo->IsNativeArray())
{
if (arrayInfo->IsNativeIntArray())
{
pNew = function->GetLibrary()->CreateNativeIntArray(callInfo.Count - 1);
}
else
{
pNew = function->GetLibrary()->CreateNativeFloatArray(callInfo.Count - 1);
}
}
else
{
pNew = function->GetLibrary()->CreateArray(callInfo.Count - 1);
}
pNew->FillFromArgs(callInfo.Count - 1, 0, args.Values, arrayInfo);
}
#ifdef VALIDATE_ARRAY
pNew->ValidateArray();
#endif
return pNew;
JIT_HELPER_END(ScrArr_ProfiledNewInstance);
}
#endif
Var JavascriptArray::NewInstance(RecyclableObject* function, CallInfo callInfo, ...)
{
ARGUMENTS(args, callInfo);
return NewInstance(function, args);
}
Var JavascriptArray::NewInstance(RecyclableObject* function, Arguments args)
{
// Call to new Array(), possibly under another name.
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
// SkipDefaultNewObject function flag should have prevented the default object
// being created, except when call true a host dispatch.
const CallInfo &callInfo = args.Info;
Var newTarget = args.GetNewTarget();
bool isCtorSuperCall = JavascriptOperators::GetAndAssertIsConstructorSuperCall(args);
ScriptContext* scriptContext = function->GetScriptContext();
JavascriptArray* pNew = nullptr;
if (callInfo.Count < 2)
{
// No arguments passed to Array(), so create with the default size (0).
pNew = CreateArrayFromConstructorNoArg(function, scriptContext);
return isCtorSuperCall ?
JavascriptOperators::OrdinaryCreateFromConstructor(VarTo<RecyclableObject>(newTarget), pNew, nullptr, scriptContext) :
pNew;
}
if (callInfo.Count == 2)
{
// Exactly one argument, which is the array length if it's a uint32.
Var firstArgument = args[1];
int elementCount;
if (TaggedInt::Is(firstArgument))
{
elementCount = TaggedInt::ToInt32(firstArgument);
if (elementCount < 0)
{
JavascriptError::ThrowRangeError(scriptContext, JSERR_ArrayLengthConstructIncorrect);
}
pNew = CreateArrayFromConstructor(function, elementCount, scriptContext);
}
else if (JavascriptNumber::Is_NoTaggedIntCheck(firstArgument))
{
// Non-tagged-int number: make sure the double value is really a uint32.
double value = JavascriptNumber::GetValue(firstArgument);
uint32 uvalue = JavascriptConversion::ToUInt32(value);
if (value != uvalue)
{
JavascriptError::ThrowRangeError(scriptContext, JSERR_ArrayLengthConstructIncorrect);
}
pNew = CreateArrayFromConstructor(function, uvalue, scriptContext);
}
else
{
//
// First element is not int/double
// create an array of length 1.
// Set first element as the passed Var
//
pNew = CreateArrayFromConstructor(function, 1, scriptContext);
JavascriptOperators::SetItem(pNew, pNew, 0u, firstArgument, scriptContext, PropertyOperation_ThrowIfNotExtensible);
// If we were passed an uninitialized JavascriptArray as the this argument,
// we need to set the length. We must do this _after_ setting the first
// element as the array may have side effects such as a setter for property
// named '0' which would make the previous length of the array observable.
// If we weren't passed a JavascriptArray as the this argument, this is no-op.
pNew->SetLength(1);
}
}
else
{
// Called with a list of initial element values.
// Create an array of the appropriate length and walk the list.
pNew = CreateArrayFromConstructor(function, callInfo.Count - 1, scriptContext);
pNew->JavascriptArray::FillFromArgs(callInfo.Count - 1, 0, args.Values);
}
#ifdef VALIDATE_ARRAY
pNew->ValidateArray();
#endif
return isCtorSuperCall ?
JavascriptOperators::OrdinaryCreateFromConstructor(VarTo<RecyclableObject>(newTarget), pNew, nullptr, scriptContext) :
pNew;
}
JavascriptArray* JavascriptArray::CreateArrayFromConstructor(RecyclableObject* constructor, uint32 length, ScriptContext* scriptContext)
{
JavascriptLibrary* library = constructor->GetLibrary();
// Create the Array object we'll return - this is the only way to create an object which is an exotic Array object.
// Note: We need to use the library from the ScriptContext of the constructor, not the currently executing function.
// This is for the case where a built-in @@create method from a different JavascriptLibrary is installed on
// constructor.
return library->CreateArray(length);
}
JavascriptArray* JavascriptArray::CreateArrayFromConstructorNoArg(RecyclableObject* constructor, ScriptContext* scriptContext)
{
JavascriptLibrary* library = constructor->GetLibrary();
return library->CreateArray();
}
#if ENABLE_PROFILE_INFO
Var JavascriptArray::ProfiledNewInstanceNoArg(RecyclableObject *function, ScriptContext *scriptContext, ArrayCallSiteInfo *arrayInfo, RecyclerWeakReference<FunctionBody> *weakFuncRef)
{
JIT_HELPER_NOT_REENTRANT_HEADER(ScrArr_ProfiledNewInstanceNoArg, reentrancylock, scriptContext->GetThreadContext());
Assert(VarIs<JavascriptFunction>(function) &&
VarTo<JavascriptFunction>(function)->GetFunctionInfo() == &JavascriptArray::EntryInfo::NewInstance);
if (arrayInfo->IsNativeIntArray())
{
JavascriptNativeIntArray *arr = scriptContext->GetLibrary()->CreateNativeIntArray();
arr->SetArrayProfileInfo(weakFuncRef, arrayInfo);
return arr;
}
if (arrayInfo->IsNativeFloatArray())
{
JavascriptNativeFloatArray *arr = scriptContext->GetLibrary()->CreateNativeFloatArray();
arr->SetArrayProfileInfo(weakFuncRef, arrayInfo);
return arr;
}
return scriptContext->GetLibrary()->CreateArray();
JIT_HELPER_END(ScrArr_ProfiledNewInstanceNoArg);
}
#endif
Var JavascriptNativeIntArray::NewInstance(RecyclableObject* function, CallInfo callInfo, ...)
{
ARGUMENTS(args, callInfo);
return NewInstance(function, args);
}
Var JavascriptNativeIntArray::NewInstance(RecyclableObject* function, Arguments args)
{
Assert(!PHASE_OFF1(NativeArrayPhase));
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
const CallInfo &callInfo = args.Info;
if (callInfo.Count < 2)
{
// No arguments passed to Array(), so create with the default size (0).
return function->GetLibrary()->CreateNativeIntArray();
}
JavascriptArray* pNew = nullptr;
if (callInfo.Count == 2)
{
// Exactly one argument, which is the array length if it's a uint32.
Var firstArgument = args[1];
int elementCount;
if (TaggedInt::Is(firstArgument))
{
elementCount = TaggedInt::ToInt32(firstArgument);
if (elementCount < 0)
{
JavascriptError::ThrowRangeError(
function->GetScriptContext(), JSERR_ArrayLengthConstructIncorrect);
}
pNew = function->GetLibrary()->CreateNativeIntArray(elementCount);
}
else if (JavascriptNumber::Is_NoTaggedIntCheck(firstArgument))
{
// Non-tagged-int number: make sure the double value is really a uint32.
double value = JavascriptNumber::GetValue(firstArgument);
uint32 uvalue = JavascriptConversion::ToUInt32(value);
if (value != uvalue)
{
JavascriptError::ThrowRangeError(
function->GetScriptContext(), JSERR_ArrayLengthConstructIncorrect);
}
pNew = function->GetLibrary()->CreateNativeIntArray(uvalue);
}
else
{
//
// First element is not int/double
// create an array of length 1.
// Set first element as the passed Var
//
pNew = function->GetLibrary()->CreateArray(1);
pNew->DirectSetItemAt<Var>(0, firstArgument);
}
}
else
{
// Called with a list of initial element values.
// Create an array of the appropriate length and walk the list.
JavascriptNativeIntArray *arr = function->GetLibrary()->CreateNativeIntArray(callInfo.Count - 1);
pNew = arr->FillFromArgs(callInfo.Count - 1, 0, args.Values);
}
#ifdef VALIDATE_ARRAY
pNew->ValidateArray();
#endif
return pNew;
}
Var JavascriptNativeFloatArray::NewInstance(RecyclableObject* function, CallInfo callInfo, ...)
{
ARGUMENTS(args, callInfo);
return NewInstance(function, args);
}
Var JavascriptNativeFloatArray::NewInstance(RecyclableObject* function, Arguments args)
{
Assert(!PHASE_OFF1(NativeArrayPhase));
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
const CallInfo &callInfo = args.Info;
if (callInfo.Count < 2)
{
// No arguments passed to Array(), so create with the default size (0).
return function->GetLibrary()->CreateNativeFloatArray();
}
JavascriptArray* pNew = nullptr;
if (callInfo.Count == 2)
{
// Exactly one argument, which is the array length if it's a uint32.
Var firstArgument = args[1];
int elementCount;
if (TaggedInt::Is(firstArgument))
{
elementCount = TaggedInt::ToInt32(firstArgument);
if (elementCount < 0)
{
JavascriptError::ThrowRangeError(
function->GetScriptContext(), JSERR_ArrayLengthConstructIncorrect);
}
pNew = function->GetLibrary()->CreateNativeFloatArray(elementCount);
}
else if (JavascriptNumber::Is_NoTaggedIntCheck(firstArgument))
{
// Non-tagged-int number: make sure the double value is really a uint32.
double value = JavascriptNumber::GetValue(firstArgument);
uint32 uvalue = JavascriptConversion::ToUInt32(value);
if (value != uvalue)
{
JavascriptError::ThrowRangeError(
function->GetScriptContext(), JSERR_ArrayLengthConstructIncorrect);
}
pNew = function->GetLibrary()->CreateNativeFloatArray(uvalue);
}
else
{
//
// First element is not int/double
// create an array of length 1.
// Set first element as the passed Var
//
pNew = function->GetLibrary()->CreateArray(1);
pNew->DirectSetItemAt<Var>(0, firstArgument);
}
}
else
{
// Called with a list of initial element values.
// Create an array of the appropriate length and walk the list.
JavascriptNativeFloatArray *arr = function->GetLibrary()->CreateNativeFloatArray(callInfo.Count - 1);
pNew = arr->FillFromArgs(callInfo.Count - 1, 0, args.Values);
}
#ifdef VALIDATE_ARRAY
pNew->ValidateArray();
#endif
return pNew;
}
#if ENABLE_PROFILE_INFO
JavascriptArray * JavascriptNativeIntArray::FillFromArgs(uint length, uint start, Var *args, ArrayCallSiteInfo *arrayInfo, bool dontCreateNewArray)
#else
JavascriptArray * JavascriptNativeIntArray::FillFromArgs(uint length, uint start, Var *args, bool dontCreateNewArray)
#endif
{
uint i;
for (i = start; i < length; i++)
{
Var item = args[i + 1];
bool isTaggedInt = TaggedInt::Is(item);
bool isTaggedIntMissingValue = false;
if (isTaggedInt)
{
int32 iValue = TaggedInt::ToInt32(item);
isTaggedIntMissingValue = Js::SparseArraySegment<int32>::IsMissingItem(&iValue);
}
if (isTaggedInt && !isTaggedIntMissingValue)
{
// This is taggedInt case and we verified that item is not missing value in AMD64.
this->DirectSetItemAt(i, TaggedInt::ToInt32(item));
}
else if (!isTaggedIntMissingValue && JavascriptNumber::Is_NoTaggedIntCheck(item))
{
double dvalue = JavascriptNumber::GetValue(item);
int32 ivalue;
if (JavascriptNumber::TryGetInt32Value(dvalue, &ivalue) && !Js::SparseArraySegment<int32>::IsMissingItem(&ivalue))
{
this->DirectSetItemAt(i, ivalue);
}
else
{
#if ENABLE_PROFILE_INFO
if (arrayInfo)
{
arrayInfo->SetIsNotNativeIntArray();
}
#endif
if (HasInlineHeadSegment(length) && i < this->head->length && !dontCreateNewArray)
{
// Avoid shrinking the number of elements in the head segment. We can still create a new
// array here, so go ahead.
JavascriptNativeFloatArray *fArr =
this->GetScriptContext()->GetLibrary()->CreateNativeFloatArrayLiteral(length);
return fArr->JavascriptNativeFloatArray::FillFromArgs(length, 0, args);
}
JavascriptNativeFloatArray *fArr = JavascriptNativeIntArray::ToNativeFloatArray(this);
fArr->DirectSetItemAt(i, dvalue);
#if ENABLE_PROFILE_INFO
return fArr->JavascriptNativeFloatArray::FillFromArgs(length, i + 1, args, arrayInfo, dontCreateNewArray);
#else
return fArr->JavascriptNativeFloatArray::FillFromArgs(length, i + 1, args, dontCreateNewArray);
#endif
}
}
else
{
#if ENABLE_PROFILE_INFO
if (arrayInfo)
{
arrayInfo->SetIsNotNativeArray();
}
#endif
#pragma prefast(suppress:6237, "The right hand side condition does not have any side effects.")
if (sizeof(int32) < sizeof(Var) && HasInlineHeadSegment(length) && i < this->head->length && !dontCreateNewArray)
{
// Avoid shrinking the number of elements in the head segment. We can still create a new
// array here, so go ahead.
JavascriptArray *arr = this->GetScriptContext()->GetLibrary()->CreateArrayLiteral(length);
return arr->JavascriptArray::FillFromArgs(length, 0, args);
}
JavascriptArray *arr = JavascriptNativeIntArray::ToVarArray(this);
#if ENABLE_PROFILE_INFO
return arr->JavascriptArray::FillFromArgs(length, i, args, nullptr, dontCreateNewArray);
#else
return arr->JavascriptArray::FillFromArgs(length, i, args, dontCreateNewArray);
#endif
}
}
return this;
}
#if ENABLE_PROFILE_INFO
JavascriptArray * JavascriptNativeFloatArray::FillFromArgs(uint length, uint start, Var *args, ArrayCallSiteInfo *arrayInfo, bool dontCreateNewArray)
#else
JavascriptArray * JavascriptNativeFloatArray::FillFromArgs(uint length, uint start, Var *args, bool dontCreateNewArray)
#endif
{
uint i;
for (i = start; i < length; i++)
{
Var item = args[i + 1];
if (TaggedInt::Is(item))
{
this->DirectSetItemAt(i, TaggedInt::ToDouble(item));
}
else if (JavascriptNumber::Is_NoTaggedIntCheck(item))
{
this->DirectSetItemAt(i, JavascriptNumber::GetValue(item));
}
else
{
JavascriptArray *arr = JavascriptNativeFloatArray::ToVarArray(this);
#if ENABLE_PROFILE_INFO
if (arrayInfo)
{
arrayInfo->SetIsNotNativeArray();
}
return arr->JavascriptArray::FillFromArgs(length, i, args, nullptr, dontCreateNewArray);
#else
return arr->JavascriptArray::FillFromArgs(length, i, args, dontCreateNewArray);
#endif
}
}
return this;
}
#if ENABLE_PROFILE_INFO
JavascriptArray * JavascriptArray::FillFromArgs(uint length, uint start, Var *args, ArrayCallSiteInfo *arrayInfo, bool dontCreateNewArray)
#else
JavascriptArray * JavascriptArray::FillFromArgs(uint length, uint start, Var *args, bool dontCreateNewArray)
#endif
{
uint32 i;
for (i = start; i < length; i++)
{
Var item = args[i + 1];
this->DirectSetItemAt(i, item);
}
return this;
}
DynamicType * JavascriptNativeIntArray::GetInitialType(ScriptContext * scriptContext)
{
return scriptContext->GetLibrary()->GetNativeIntArrayType();
}
#if ENABLE_COPYONACCESS_ARRAY
DynamicType * JavascriptCopyOnAccessNativeIntArray::GetInitialType(ScriptContext * scriptContext)
{
return scriptContext->GetLibrary()->GetCopyOnAccessNativeIntArrayType();
}
#endif
JavascriptNativeFloatArray *JavascriptNativeIntArray::ToNativeFloatArray(JavascriptNativeIntArray *intArray)
{
ScriptContext *scriptContext = intArray->GetScriptContext();
JIT_HELPER_NOT_REENTRANT_HEADER(IntArr_ToNativeFloatArray, reentrancylock, scriptContext->GetThreadContext());
#if ENABLE_PROFILE_INFO
ArrayCallSiteInfo *arrayInfo = intArray->GetArrayCallSiteInfo();
if (arrayInfo)
{
#if DBG
Js::JavascriptStackWalker walker(intArray->GetScriptContext());
Js::JavascriptFunction* caller = nullptr;
bool foundScriptCaller = false;
while(walker.GetCaller(&caller))
{
if(caller != nullptr && Js::ScriptFunction::Test(caller))
{
foundScriptCaller = true;
break;
}
}
if(foundScriptCaller)
{
Assert(caller);
Assert(caller->GetFunctionBody());
if(PHASE_TRACE(Js::NativeArrayConversionPhase, caller->GetFunctionBody()))
{
Output::Print(_u("Conversion: Int array to Float array ArrayCreationFunctionNumber:%2d CallSiteNumber:%2d \n"), arrayInfo->functionNumber, arrayInfo->callSiteNumber);
Output::Flush();
}
}
else
{
if(PHASE_TRACE1(Js::NativeArrayConversionPhase))
{
Output::Print(_u("Conversion: Int array to Float array across ScriptContexts"));
Output::Flush();
}
}
#else
if(PHASE_TRACE1(Js::NativeArrayConversionPhase))
{
Output::Print(_u("Conversion: Int array to Float array"));
Output::Flush();
}
#endif
arrayInfo->SetIsNotNativeIntArray();
}
#endif
// Code below has potential to throw due to OOM or SO. Just FailFast on those cases
AutoDisableInterrupt failFastError(scriptContext->GetThreadContext());
// Grow the segments
Recycler *recycler = scriptContext->GetRecycler();
SparseArraySegmentBase *seg, *nextSeg, *prevSeg = nullptr;
for (seg = intArray->head; seg; seg = nextSeg)
{
nextSeg = seg->next;
uint32 size = seg->size;
if (size == 0)
{
continue;
}
uint32 left = seg->left;
uint32 length = seg->length;
int i;
int32 ival;
// The old segment will have size/2 and length capped by the new size.
uint32 newSegSize = seg->size >> 1;
if (seg == intArray->head || seg->length > (newSegSize >> 1))
{
// Some live elements are being pushed out of this segment, so allocate a new one.
SparseArraySegment<double> *newSeg =
SparseArraySegment<double>::AllocateSegment(recycler, left, length, nextSeg);
Assert(newSeg != nullptr);
Assert((prevSeg == nullptr) == (seg == intArray->head));
newSeg->next = nextSeg;
intArray->LinkSegments((SparseArraySegment<double>*)prevSeg, newSeg);
if (intArray->GetLastUsedSegment() == seg)
{
intArray->SetLastUsedSegment(newSeg);
}
prevSeg = newSeg;
SegmentBTree * segmentMap = intArray->GetSegmentMap();
if (segmentMap)
{
segmentMap->SwapSegment(left, seg, newSeg);
}
// Fill the new segment with the overflow.
for (i = 0; (uint)i < newSeg->length; i++)
{
ival = ((SparseArraySegment<int32>*)seg)->elements[i /*+ seg->length*/];
if (ival == JavascriptNativeIntArray::MissingItem)
{
continue;
}
newSeg->elements[i] = (double)ival;
}
}
else
{
seg->size = newSegSize >> 1;
seg->CheckLengthvsSize();
// Now convert the contents that will remain in the old segment.
for (i = seg->length - 1; i >= 0; i--)
{
ival = ((SparseArraySegment<int32>*)seg)->elements[i];
if (ival == JavascriptNativeIntArray::MissingItem)
{
((SparseArraySegment<double>*)seg)->elements[i] = (double)JavascriptNativeFloatArray::MissingItem;
}
else
{
((SparseArraySegment<double>*)seg)->elements[i] = (double)ival;
}
}
prevSeg = seg;
}
}
if (intArray->GetType() == scriptContext->GetLibrary()->GetNativeIntArrayType())
{
intArray->type = scriptContext->GetLibrary()->GetNativeFloatArrayType();
}
else
{
if (intArray->GetDynamicType()->GetIsLocked())
{
DynamicTypeHandler *typeHandler = intArray->GetDynamicType()->GetTypeHandler();
if (typeHandler->IsPathTypeHandler())
{
// We can't allow a type with the new type ID to be promoted to the old type.
// So go to a dictionary type handler, which will orphan the new type.
// This should be a corner case, so the inability to share the new type is unlikely to matter.
// If it does matter, try building a path from the new type's built-in root.
static_cast<PathTypeHandlerBase*>(typeHandler)->ResetTypeHandler(intArray);
}
else
{
intArray->ChangeType();
}
}
intArray->GetType()->SetTypeId(TypeIds_NativeFloatArray);
}
if (CrossSite::IsCrossSiteObjectTyped(intArray))
{
Assert(VirtualTableInfo<CrossSiteObject<JavascriptNativeIntArray>>::HasVirtualTable(intArray));
VirtualTableInfo<CrossSiteObject<JavascriptNativeFloatArray>>::SetVirtualTable(intArray);
}
else
{
Assert(VirtualTableInfo<JavascriptNativeIntArray>::HasVirtualTable(intArray));
VirtualTableInfo<JavascriptNativeFloatArray>::SetVirtualTable(intArray);
}
failFastError.Completed();
return (JavascriptNativeFloatArray*)intArray;
JIT_HELPER_END(IntArr_ToNativeFloatArray);
}
/*
* JavascriptArray::ChangeArrayTypeToNativeArray<double>
* - Converts the Var Array's type to NativeFloat.
* - Sets the VirtualTable to "JavascriptNativeFloatArray"
*/
template<>
void JavascriptArray::ChangeArrayTypeToNativeArray<double>(JavascriptArray * varArray, ScriptContext * scriptContext)
{
AssertMsg(!VarIs<JavascriptNativeArray>(varArray), "Ensure that the incoming Array is a Var array");
if (varArray->GetType() == scriptContext->GetLibrary()->GetArrayType())
{
varArray->type = scriptContext->GetLibrary()->GetNativeFloatArrayType();
}
else
{
if (varArray->GetDynamicType()->GetIsLocked())
{
DynamicTypeHandler *typeHandler = varArray->GetDynamicType()->GetTypeHandler();
if (typeHandler->IsPathTypeHandler())
{
// We can't allow a type with the new type ID to be promoted to the old type.
// So go to a dictionary type handler, which will orphan the new type.
// This should be a corner case, so the inability to share the new type is unlikely to matter.
// If it does matter, try building a path from the new type's built-in root.
static_cast<PathTypeHandlerBase*>(typeHandler)->ResetTypeHandler(varArray);
}
else
{
varArray->ChangeType();
}
}
varArray->GetType()->SetTypeId(TypeIds_NativeFloatArray);
}
if (CrossSite::IsCrossSiteObjectTyped(varArray))
{
Assert(VirtualTableInfo<CrossSiteObject<JavascriptArray>>::HasVirtualTable(varArray));
VirtualTableInfo<CrossSiteObject<JavascriptNativeFloatArray>>::SetVirtualTable(varArray);
}
else
{
Assert(VirtualTableInfo<JavascriptArray>::HasVirtualTable(varArray));
VirtualTableInfo<JavascriptNativeFloatArray>::SetVirtualTable(varArray);
}
}
/*
* JavascriptArray::ChangeArrayTypeToNativeArray<int32>
* - Converts the Var Array's type to NativeInt.
* - Sets the VirtualTable to "JavascriptNativeIntArray"
*/
template<>
void JavascriptArray::ChangeArrayTypeToNativeArray<int32>(JavascriptArray * varArray, ScriptContext * scriptContext)
{
AssertMsg(!VarIs<JavascriptNativeArray>(varArray), "Ensure that the incoming Array is a Var array");
if (varArray->GetType() == scriptContext->GetLibrary()->GetArrayType())
{
varArray->type = scriptContext->GetLibrary()->GetNativeIntArrayType();
}
else
{
if (varArray->GetDynamicType()->GetIsLocked())
{
DynamicTypeHandler *typeHandler = varArray->GetDynamicType()->GetTypeHandler();
if (typeHandler->IsPathTypeHandler())
{
// We can't allow a type with the new type ID to be promoted to the old type.
// So go to a dictionary type handler, which will orphan the new type.
// This should be a corner case, so the inability to share the new type is unlikely to matter.
// If it does matter, try building a path from the new type's built-in root.
static_cast<PathTypeHandlerBase*>(typeHandler)->ResetTypeHandler(varArray);
}
else
{
varArray->ChangeType();
}
}
varArray->GetType()->SetTypeId(TypeIds_NativeIntArray);
}
if (CrossSite::IsCrossSiteObjectTyped(varArray))
{
Assert(VirtualTableInfo<CrossSiteObject<JavascriptArray>>::HasVirtualTable(varArray));
VirtualTableInfo<CrossSiteObject<JavascriptNativeIntArray>>::SetVirtualTable(varArray);
}
else
{
Assert(VirtualTableInfo<JavascriptArray>::HasVirtualTable(varArray));
VirtualTableInfo<JavascriptNativeIntArray>::SetVirtualTable(varArray);
}
}
template<>
int32 JavascriptArray::GetNativeValue<int32>(Js::Var ival, ScriptContext * scriptContext)
{
return JavascriptConversion::ToInt32(ival, scriptContext);
}
template <>
double JavascriptArray::GetNativeValue<double>(Var ival, ScriptContext * scriptContext)
{
return JavascriptConversion::ToNumber(ival, scriptContext);
}
/*
* JavascriptArray::ConvertToNativeArrayInPlace
* In place conversion of all Var elements to Native Int/Double elements in an array.
* We do not update the DynamicProfileInfo of the array here.
*/
template<typename NativeArrayType, typename T>
NativeArrayType *JavascriptArray::ConvertToNativeArrayInPlace(JavascriptArray *varArray)
{
AssertMsg(!VarIs<JavascriptNativeArray>(varArray), "Ensure that the incoming Array is a Var array");
ScriptContext *scriptContext = varArray->GetScriptContext();
SparseArraySegmentBase *seg, *nextSeg, *prevSeg = nullptr;
for (seg = varArray->head; seg; seg = nextSeg)
{
nextSeg = seg->next;
uint32 size = seg->size;
if (size == 0)
{
continue;
}
int i;
Var ival;
uint32 growFactor = sizeof(Var) / sizeof(T);
AssertMsg(growFactor == 1, "We support only in place conversion of Var array to Native Array");
// Now convert the contents that will remain in the old segment.
for (i = seg->length - 1; i >= 0; i--)
{
ival = ((SparseArraySegment<Var>*)seg)->elements[i];
if (ival == JavascriptArray::MissingItem)
{
((SparseArraySegment<T>*)seg)->elements[i] = NativeArrayType::MissingItem;
}
else
{
((SparseArraySegment<T>*)seg)->elements[i] = GetNativeValue<T>(ival, scriptContext);
}
}
prevSeg = seg;
}
// Update the type of the Array
ChangeArrayTypeToNativeArray<T>(varArray, scriptContext);
return (NativeArrayType*)varArray;
}
JavascriptArray *JavascriptNativeIntArray::ConvertToVarArray(JavascriptNativeIntArray *intArray)
{
#if ENABLE_COPYONACCESS_ARRAY
JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(intArray);
#endif
ScriptContext *scriptContext = intArray->GetScriptContext();
Recycler *recycler = scriptContext->GetRecycler();
SparseArraySegmentBase *seg, *nextSeg, *prevSeg = nullptr;
// Code below has potential to throw due to OOM or SO. Just FailFast on those cases
AutoDisableInterrupt failFastError(scriptContext->GetThreadContext());
for (seg = intArray->head; seg; seg = nextSeg)
{
nextSeg = seg->next;
uint32 size = seg->size;
if (size == 0)
{
continue;
}
uint32 left = seg->left;
uint32 length = seg->length;
int i;
int32 ival;
// Shrink?
uint32 growFactor = sizeof(Var) / sizeof(int32);
if ((growFactor != 1 && (seg == intArray->head || seg->length > (seg->size / growFactor))) ||
(seg->next == nullptr && SparseArraySegmentBase::IsLeafSegment(seg, recycler)))
{
// Some live elements are being pushed out of this segment, so allocate a new one.
// And/or the old segment is not scanned by the recycler, so we need a new one to hold vars.
SparseArraySegment<Var> *newSeg =
SparseArraySegment<Var>::AllocateSegment(recycler, left, length, nextSeg);
AnalysisAssert(newSeg);
// Fill the new segment with the overflow.
for (i = 0; (uint)i < newSeg->length; i++)
{
ival = ((SparseArraySegment<int32>*)seg)->elements[i];
if (ival == JavascriptNativeIntArray::MissingItem)
{
continue;
}
newSeg->elements[i] = JavascriptNumber::ToVar(ival, scriptContext);
}
// seg elements are copied over, now it is safe to replace seg with newSeg.
// seg could be GC collected if replaced by newSeg.
Assert((prevSeg == nullptr) == (seg == intArray->head));
newSeg->next = nextSeg;
intArray->LinkSegments((SparseArraySegment<Var>*)prevSeg, newSeg);
if (intArray->GetLastUsedSegment() == seg)
{
intArray->SetLastUsedSegment(newSeg);
}
prevSeg = newSeg;
SegmentBTree * segmentMap = intArray->GetSegmentMap();
if (segmentMap)
{
segmentMap->SwapSegment(left, seg, newSeg);
}
}
else
{
seg->size = seg->size / growFactor;
seg->CheckLengthvsSize();
// Now convert the contents that will remain in the old segment.
// Walk backward in case we're growing the element size.
for (i = seg->length - 1; i >= 0; i--)
{
ival = ((SparseArraySegment<int32>*)seg)->elements[i];
if (ival == JavascriptNativeIntArray::MissingItem)
{
((SparseArraySegment<Var>*)seg)->elements[i] = (Var)JavascriptArray::MissingItem;
}
else
{
((SparseArraySegment<Var>*)seg)->elements[i] = JavascriptNumber::ToVar(ival, scriptContext);
}
SparseArraySegment<Var>* newSeg = (SparseArraySegment<Var>*)seg;
newSeg->FillSegmentBuffer(seg->length, seg->size);
}
prevSeg = seg;
}
}
if (intArray->GetType() == scriptContext->GetLibrary()->GetNativeIntArrayType())
{
intArray->type = scriptContext->GetLibrary()->GetArrayType();
}
else
{
if (intArray->GetDynamicType()->GetIsLocked())
{
DynamicTypeHandler *typeHandler = intArray->GetDynamicType()->GetTypeHandler();
if (typeHandler->IsPathTypeHandler())
{
// We can't allow a type with the new type ID to be promoted to the old type.
// So go to a dictionary type handler, which will orphan the new type.
// This should be a corner case, so the inability to share the new type is unlikely to matter.
// If it does matter, try building a path from the new type's built-in root.
static_cast<PathTypeHandlerBase*>(typeHandler)->ResetTypeHandler(intArray);
}
else
{
intArray->ChangeType();
}
}
intArray->GetType()->SetTypeId(TypeIds_Array);
}
if (CrossSite::IsCrossSiteObjectTyped(intArray))
{
Assert(VirtualTableInfo<CrossSiteObject<JavascriptNativeIntArray>>::HasVirtualTable(intArray));
VirtualTableInfo<CrossSiteObject<JavascriptArray>>::SetVirtualTable(intArray);
}
else
{
Assert(VirtualTableInfo<JavascriptNativeIntArray>::HasVirtualTable(intArray));
VirtualTableInfo<JavascriptArray>::SetVirtualTable(intArray);
}
failFastError.Completed();
return intArray;
}
JavascriptArray *JavascriptNativeIntArray::ToVarArray(JavascriptNativeIntArray *intArray)
{
JIT_HELPER_NOT_REENTRANT_HEADER(IntArr_ToVarArray, reentrancylock, intArray->GetScriptContext()->GetThreadContext());
#if ENABLE_PROFILE_INFO
ArrayCallSiteInfo *arrayInfo = intArray->GetArrayCallSiteInfo();
if (arrayInfo)
{
#if DBG
Js::JavascriptStackWalker walker(intArray->GetScriptContext());
Js::JavascriptFunction* caller = nullptr;
bool foundScriptCaller = false;
while(walker.GetCaller(&caller))
{
if(caller != nullptr && Js::ScriptFunction::Test(caller))
{
foundScriptCaller = true;
break;
}
}
if(foundScriptCaller)
{
Assert(caller);
Assert(caller->GetFunctionBody());
if(PHASE_TRACE(Js::NativeArrayConversionPhase, caller->GetFunctionBody()))
{
Output::Print(_u("Conversion: Int array to Var array ArrayCreationFunctionNumber:%2d CallSiteNumber:%2d \n"), arrayInfo->functionNumber, arrayInfo->callSiteNumber);
Output::Flush();
}
}
else
{
if(PHASE_TRACE1(Js::NativeArrayConversionPhase))
{
Output::Print(_u("Conversion: Int array to Var array across ScriptContexts"));
Output::Flush();
}
}
#else
if(PHASE_TRACE1(Js::NativeArrayConversionPhase))
{
Output::Print(_u("Conversion: Int array to Var array"));
Output::Flush();
}
#endif
arrayInfo->SetIsNotNativeArray();
}
#endif
intArray->ClearArrayCallSiteIndex();
return ConvertToVarArray(intArray);
JIT_HELPER_END(IntArr_ToVarArray);
}
DynamicType * JavascriptNativeFloatArray::GetInitialType(ScriptContext * scriptContext)
{
return scriptContext->GetLibrary()->GetNativeFloatArrayType();
}
/*
* JavascriptNativeFloatArray::ConvertToVarArray
* This function only converts all Float elements to Var elements in an array.
* DynamicProfileInfo of the array is not updated in this function.
*/
JavascriptArray *JavascriptNativeFloatArray::ConvertToVarArray(JavascriptNativeFloatArray *fArray)
{
// We can't be growing the size of the element.
Assert(sizeof(double) >= sizeof(Var));
uint32 shrinkFactor = sizeof(double) / sizeof(Var);
ScriptContext *scriptContext = fArray->GetScriptContext();
Recycler *recycler = scriptContext->GetRecycler();
SparseArraySegmentBase *seg, *nextSeg, *prevSeg = nullptr;
// Code below has potential to throw due to OOM or SO. Just FailFast on those cases
AutoDisableInterrupt failFastError(scriptContext->GetThreadContext());
#if defined(TARGET_32)
if (fArray->head && (fArray->head->size >= SparseArraySegmentBase::INLINE_CHUNK_SIZE / shrinkFactor))
{
CopyHeadIfInlinedHeadSegment<double>(fArray, recycler);
}
#endif
for (seg = fArray->head; seg; seg = nextSeg)
{
nextSeg = seg->next;
if (seg->size == 0)
{
continue;
}
uint32 left = seg->left;
uint32 length = seg->length;
SparseArraySegment<Var> *newSeg = nullptr;
if (seg->next == nullptr && SparseArraySegmentBase::IsLeafSegment(seg, recycler))
{
// The old segment is not scanned by the recycler, so we need a new one to hold vars.
newSeg =
SparseArraySegment<Var>::AllocateSegment(recycler, left, length, nextSeg);
}
else
{
newSeg = (SparseArraySegment<Var>*)seg;
prevSeg = seg;
if (shrinkFactor != 1)
{
uint32 newSize = seg->size * shrinkFactor;
uint32 limit;
if (seg->next)
{
limit = seg->next->left;
}
else
{
limit = JavascriptArray::MaxArrayLength;
}
seg->size = min(newSize, limit - seg->left);
seg->CheckLengthvsSize();
}
}
uint32 i;
for (i = 0; i < seg->length; i++)
{
if (SparseArraySegment<double>::IsMissingItem(&((SparseArraySegment<double>*)seg)->elements[i]))
{
if (seg == newSeg)
{
newSeg->elements[i] = (Var)JavascriptArray::MissingItem;
}
Assert(newSeg->elements[i] == (Var)JavascriptArray::MissingItem);
}
else if (*(uint64*)&(((SparseArraySegment<double>*)seg)->elements[i]) == 0ull)
{
newSeg->elements[i] = TaggedInt::ToVarUnchecked(0);
}
else
{
int32 ival;
double dval = ((SparseArraySegment<double>*)seg)->elements[i];
if (JavascriptNumber::TryGetInt32Value(dval, &ival) && !TaggedInt::IsOverflow(ival))
{
newSeg->elements[i] = TaggedInt::ToVarUnchecked(ival);
}
else
{
newSeg->elements[i] = JavascriptNumber::ToVarWithCheck(dval, scriptContext);
}
}
}
if (seg == newSeg)
{
// Fill the remaining slots.
newSeg->FillSegmentBuffer(i, seg->size);
}
// seg elements are copied over, now it is safe to replace seg with newSeg.
// seg could be GC collected if replaced by newSeg.
if (newSeg != seg)
{
Assert((prevSeg == nullptr) == (seg == fArray->head));
newSeg->next = nextSeg;
fArray->LinkSegments((SparseArraySegment<Var>*)prevSeg, newSeg);
if (fArray->GetLastUsedSegment() == seg)
{
fArray->SetLastUsedSegment(newSeg);
}
prevSeg = newSeg;
SegmentBTree * segmentMap = fArray->GetSegmentMap();
if (segmentMap)
{
segmentMap->SwapSegment(left, seg, newSeg);
}
}
}
if (fArray->GetType() == scriptContext->GetLibrary()->GetNativeFloatArrayType())
{
fArray->type = scriptContext->GetLibrary()->GetArrayType();
}
else
{
if (fArray->GetDynamicType()->GetIsLocked())
{
DynamicTypeHandler *typeHandler = fArray->GetDynamicType()->GetTypeHandler();
if (typeHandler->IsPathTypeHandler())
{
// We can't allow a type with the new type ID to be promoted to the old type.
// So go to a dictionary type handler, which will orphan the new type.
// This should be a corner case, so the inability to share the new type is unlikely to matter.
// If it does matter, try building a path from the new type's built-in root.
static_cast<PathTypeHandlerBase*>(typeHandler)->ResetTypeHandler(fArray);
}
else
{
fArray->ChangeType();
}
}
fArray->GetType()->SetTypeId(TypeIds_Array);
}
if (CrossSite::IsCrossSiteObjectTyped(fArray))
{
Assert(VirtualTableInfo<CrossSiteObject<JavascriptNativeFloatArray>>::HasVirtualTable(fArray));
VirtualTableInfo<CrossSiteObject<JavascriptArray>>::SetVirtualTable(fArray);
}
else
{
Assert(VirtualTableInfo<JavascriptNativeFloatArray>::HasVirtualTable(fArray));
VirtualTableInfo<JavascriptArray>::SetVirtualTable(fArray);
}
failFastError.Completed();
return fArray;
}
JavascriptArray *JavascriptNativeFloatArray::ToVarArray(JavascriptNativeFloatArray *fArray)
{
JIT_HELPER_NOT_REENTRANT_HEADER(FloatArr_ToVarArray, reentrancylock, fArray->GetScriptContext()->GetThreadContext());
#if ENABLE_PROFILE_INFO
ArrayCallSiteInfo *arrayInfo = fArray->GetArrayCallSiteInfo();
if (arrayInfo)
{
#if DBG
Js::JavascriptStackWalker walker(fArray->GetScriptContext());
Js::JavascriptFunction* caller = nullptr;
bool foundScriptCaller = false;
while(walker.GetCaller(&caller))
{
if(caller != nullptr && Js::ScriptFunction::Test(caller))
{
foundScriptCaller = true;
break;
}
}
if(foundScriptCaller)
{
Assert(caller);
Assert(caller->GetFunctionBody());
if(PHASE_TRACE(Js::NativeArrayConversionPhase, caller->GetFunctionBody()))
{
Output::Print(_u("Conversion: Float array to Var array ArrayCreationFunctionNumber:%2d CallSiteNumber:%2d \n"), arrayInfo->functionNumber, arrayInfo->callSiteNumber);
Output::Flush();
}
}
else
{
if(PHASE_TRACE1(Js::NativeArrayConversionPhase))
{
Output::Print(_u("Conversion: Float array to Var array across ScriptContexts"));
Output::Flush();
}
}
#else
if(PHASE_TRACE1(Js::NativeArrayConversionPhase))
{
Output::Print(_u("Conversion: Float array to Var array"));
Output::Flush();
}
#endif
if(fArray->GetScriptContext()->IsScriptContextInNonDebugMode())
{
Assert(!arrayInfo->IsNativeIntArray());
}
arrayInfo->SetIsNotNativeArray();
}
#endif
fArray->ClearArrayCallSiteIndex();
return ConvertToVarArray(fArray);
JIT_HELPER_END(FloatArr_ToVarArray);
}
// Convert Var to index in the Array.
// Note: Spec calls out a few rules for these parameters:
// 1. if (arg > length) { return length; }
// clamp to length, not length-1
// 2. if (arg < 0) { return max(0, length + arg); }
// treat negative arg as index from the end of the array (with -1 mapping to length-1)
// Effectively, this function will return a value between 0 and length, inclusive.
int64 JavascriptArray::GetIndexFromVar(Js::Var arg, int64 length, ScriptContext* scriptContext)
{
int64 index;
if (TaggedInt::Is(arg))
{
int intValue = TaggedInt::ToInt32(arg);
if (intValue < 0)
{
index = max<int64>(0, length + intValue);
}
else
{
index = intValue;
}
if (index > length)
{
index = length;
}
}
else
{
double doubleValue = JavascriptConversion::ToInteger(arg, scriptContext);
// Handle the Number.POSITIVE_INFINITY case
if (doubleValue > length)
{
return length;
}
index = NumberUtilities::TryToInt64(doubleValue);
if (index < 0)
{
index = max<int64>(0, index + length);
}
}
return index;
}
TypeId JavascriptArray::OP_SetNativeIntElementC(JavascriptNativeIntArray *arr, uint32 index, Var value, ScriptContext *scriptContext)
{
JIT_HELPER_NOT_REENTRANT_HEADER(ScrArr_SetNativeIntElementC, reentrancylock, scriptContext->GetThreadContext());
int32 iValue;
double dValue;
TypeId typeId = arr->TrySetNativeIntArrayItem(value, &iValue, &dValue);
if (typeId == TypeIds_NativeIntArray)
{
arr->SetArrayLiteralItem(index, iValue);
}
else if (typeId == TypeIds_NativeFloatArray)
{
arr->SetArrayLiteralItem(index, dValue);
}
else
{
arr->SetArrayLiteralItem(index, value);
}
return typeId;
JIT_HELPER_END(ScrArr_SetNativeIntElementC);
}
TypeId JavascriptArray::OP_SetNativeFloatElementC(JavascriptNativeFloatArray *arr, uint32 index, Var value, ScriptContext *scriptContext)
{
JIT_HELPER_NOT_REENTRANT_HEADER(ScrArr_SetNativeFloatElementC, reentrancylock, scriptContext->GetThreadContext());
double dValue;
TypeId typeId = arr->TrySetNativeFloatArrayItem(value, &dValue);
if (typeId == TypeIds_NativeFloatArray)
{
arr->SetArrayLiteralItem(index, dValue);
}
else
{
arr->SetArrayLiteralItem(index, value);
}
return typeId;
JIT_HELPER_END(ScrArr_SetNativeFloatElementC);
}
template<typename T>
void JavascriptArray::SetArrayLiteralItem(uint32 index, T value)
{
SparseArraySegment<T> * segment = SparseArraySegment<T>::From(this->head);
Assert(segment->left == 0);
Assert(index < segment->length);
segment->elements[index] = value;
}
void JavascriptNativeIntArray::SetIsPrototype()
{
// Force the array to be non-native to simplify inspection, filling from proto, etc.
ToVarArray(this);
__super::SetIsPrototype();
}
void JavascriptNativeFloatArray::SetIsPrototype()
{
// Force the array to be non-native to simplify inspection, filling from proto, etc.
ToVarArray(this);
__super::SetIsPrototype();
}
#if ENABLE_PROFILE_INFO
ArrayCallSiteInfo *JavascriptNativeArray::GetArrayCallSiteInfo()
{
RecyclerWeakReference<FunctionBody> *weakRef = this->weakRefToFuncBody;
if (weakRef)
{
FunctionBody *functionBody = weakRef->Get();
if (functionBody)
{
if (functionBody->HasDynamicProfileInfo())
{
Js::ProfileId profileId = this->GetArrayCallSiteIndex();
if (profileId < functionBody->GetProfiledArrayCallSiteCount())
{
return functionBody->GetAnyDynamicProfileInfo()->GetArrayCallSiteInfo(functionBody, profileId);
}
}
}
else
{
this->ClearArrayCallSiteIndex();
}
}
return nullptr;
}
void JavascriptNativeArray::SetArrayProfileInfo(RecyclerWeakReference<FunctionBody> *weakRef, ArrayCallSiteInfo *arrayInfo)
{
Assert(weakRef);
FunctionBody *functionBody = weakRef->Get();
if (functionBody && functionBody->HasDynamicProfileInfo())
{
ArrayCallSiteInfo *baseInfo = functionBody->GetAnyDynamicProfileInfo()->GetArrayCallSiteInfo(functionBody, 0);
Js::ProfileId index = (Js::ProfileId)(arrayInfo - baseInfo);
Assert(index < functionBody->GetProfiledArrayCallSiteCount());
SetArrayCallSite(index, weakRef);
}
}
void JavascriptNativeArray::CopyArrayProfileInfo(Js::JavascriptNativeArray* baseArray)
{
if (baseArray->weakRefToFuncBody)
{
if (baseArray->weakRefToFuncBody->Get())
{
SetArrayCallSite(baseArray->GetArrayCallSiteIndex(), baseArray->weakRefToFuncBody);
}
else
{
baseArray->ClearArrayCallSiteIndex();
}
}
}
#endif
Var JavascriptNativeArray::FindMinOrMax(Js::ScriptContext * scriptContext, bool findMax)
{
if (VarIs<JavascriptNativeIntArray>(this))
{
return this->FindMinOrMax<int32, false>(scriptContext, findMax);
}
else
{
return this->FindMinOrMax<double, true>(scriptContext, findMax);
}
}
template <typename T, bool checkNaNAndNegZero>
Var JavascriptNativeArray::FindMinOrMax(Js::ScriptContext * scriptContext, bool findMax)
{
AssertMsg(this->HasNoMissingValues(), "Fastpath is only for arrays with one segment and no missing values");
uint len = this->GetLength();
Js::SparseArraySegment<T>* headSegment = ((Js::SparseArraySegment<T>*)this->GetHead());
uint headSegLen = headSegment->length;
Assert(headSegLen == len);
if (headSegment->next == nullptr)
{
T currentRes = headSegment->elements[0];
for (uint i = 0; i < headSegLen; i++)
{
T compare = headSegment->elements[i];
if (checkNaNAndNegZero && JavascriptNumber::IsNan(double(compare)))
{
return scriptContext->GetLibrary()->GetNaN();
}
if (findMax ? currentRes < compare : currentRes > compare ||
(checkNaNAndNegZero && compare == 0 && Js::JavascriptNumber::IsNegZero(double(currentRes))))
{
currentRes = compare;
}
}
return Js::JavascriptNumber::ToVarNoCheck(currentRes, scriptContext);
}
else
{
AssertMsg(false, "FindMinOrMax currently supports native arrays with only one segment");
Throw::FatalInternalError();
}
}
SparseArraySegmentBase * JavascriptArray::GetLastUsedSegment() const
{
return HasSegmentMap() ?
PointerValue(segmentUnion.segmentBTreeRoot->lastUsedSegment) :
PointerValue(segmentUnion.lastUsedSegment);
}
void JavascriptArray::SetHeadAndLastUsedSegment(SparseArraySegmentBase * segment)
{
Assert(!HasSegmentMap());
this->head = this->segmentUnion.lastUsedSegment = segment;
}
void JavascriptArray::SetLastUsedSegment(SparseArraySegmentBase * segment)
{
if (HasSegmentMap())
{
this->segmentUnion.segmentBTreeRoot->lastUsedSegment = segment;
}
else
{
this->segmentUnion.lastUsedSegment = segment;
}
}
bool JavascriptArray::HasSegmentMap() const
{
return !!(GetFlags() & DynamicObjectFlags::HasSegmentMap);
}
SegmentBTreeRoot * JavascriptArray::GetSegmentMap() const
{
return (HasSegmentMap() ? segmentUnion.segmentBTreeRoot : nullptr);
}
void JavascriptArray::SetSegmentMap(SegmentBTreeRoot * segmentMap)
{
Assert(!HasSegmentMap());
SparseArraySegmentBase * lastUsedSeg = this->segmentUnion.lastUsedSegment;
SetFlags(GetFlags() | DynamicObjectFlags::HasSegmentMap);
segmentUnion.segmentBTreeRoot = segmentMap;
segmentMap->lastUsedSegment = lastUsedSeg;
}
void JavascriptArray::ClearSegmentMap()
{
if (HasSegmentMap())
{
SetFlags(GetFlags() & ~DynamicObjectFlags::HasSegmentMap);
SparseArraySegmentBase * lastUsedSeg = segmentUnion.segmentBTreeRoot->lastUsedSegment;
segmentUnion.segmentBTreeRoot = nullptr;
segmentUnion.lastUsedSegment = lastUsedSeg;
}
}
SegmentBTreeRoot * JavascriptArray::BuildSegmentMap()
{
Recycler* recycler = GetRecycler();
SegmentBTreeRoot* tmpSegmentMap = AllocatorNewStruct(Recycler, recycler, SegmentBTreeRoot);
ForEachSegment([recycler, tmpSegmentMap](SparseArraySegmentBase * current)
{
tmpSegmentMap->Add(recycler, current);
return false;
});
// There could be OOM during building segment map. Save to array only after its successful completion.
SetSegmentMap(tmpSegmentMap);
return tmpSegmentMap;
}
void JavascriptArray::TryAddToSegmentMap(Recycler* recycler, SparseArraySegmentBase* seg)
{
SegmentBTreeRoot * savedSegmentMap = GetSegmentMap();
if (savedSegmentMap)
{
//
// We could OOM and throw when adding to segmentMap, resulting in a corrupted segmentMap on this
// array. Set segmentMap to null temporarily to protect from this. It will be restored correctly
// if adding segment succeeds.
//
ClearSegmentMap();
savedSegmentMap->Add(recycler, seg);
SetSegmentMap(savedSegmentMap);
}
}
void JavascriptArray::InvalidateLastUsedSegment()
{
this->SetLastUsedSegment(this->head);
}
DescriptorFlags JavascriptArray::GetSetter(PropertyId propertyId, Var *setterValue, PropertyValueInfo* info, ScriptContext* requestContext)
{
DescriptorFlags flags;
if (GetSetterBuiltIns(propertyId, info, &flags))
{
return flags;
}
return __super::GetSetter(propertyId, setterValue, info, requestContext);
}
DescriptorFlags JavascriptArray::GetSetter(JavascriptString* propertyNameString, Var *setterValue, PropertyValueInfo* info, ScriptContext* requestContext)
{
DescriptorFlags flags;
PropertyRecord const* propertyRecord;
this->GetScriptContext()->FindPropertyRecord(propertyNameString, &propertyRecord);
if (propertyRecord != nullptr && GetSetterBuiltIns(propertyRecord->GetPropertyId(), info, &flags))
{
return flags;
}
return __super::GetSetter(propertyNameString, setterValue, info, requestContext);
}
bool JavascriptArray::GetSetterBuiltIns(PropertyId propertyId, PropertyValueInfo* info, DescriptorFlags* descriptorFlags)
{
if (propertyId == PropertyIds::length)
{
PropertyValueInfo::SetNoCache(info, this);
*descriptorFlags = WritableData;
return true;
}
return false;
}
SparseArraySegmentBase * JavascriptArray::GetBeginLookupSegment(uint32 index, const bool useSegmentMap) const
{
SparseArraySegmentBase *seg = nullptr;
SparseArraySegmentBase * lastUsedSeg = this->GetLastUsedSegment();
if (lastUsedSeg != nullptr && lastUsedSeg->left <= index)
{
seg = lastUsedSeg;
if(index - lastUsedSeg->left < lastUsedSeg->size)
{
return seg;
}
}
SegmentBTreeRoot * segmentMap = GetSegmentMap();
if(!useSegmentMap || !segmentMap)
{
return seg ? seg : PointerValue(this->head);
}
if(seg)
{
// If indexes are being accessed sequentially, check the segment after the last-used segment before checking the
// segment map, as it is likely to hit
SparseArraySegmentBase *const nextSeg = seg->next;
if(nextSeg)
{
if(index < nextSeg->left)
{
return seg;
}
else if(index - nextSeg->left < nextSeg->size)
{
return nextSeg;
}
}
}
SparseArraySegmentBase *matchOrNextSeg;
segmentMap->Find(index, seg, matchOrNextSeg);
return seg ? seg : matchOrNextSeg;
}
uint32 JavascriptArray::GetNextIndex(uint32 index) const
{
if (VarIs<JavascriptNativeIntArray>((Var)this))
{
return this->GetNextIndexHelper<int32>(index);
}
else if (VarIs<JavascriptNativeFloatArray>((Var)this))
{
return this->GetNextIndexHelper<double>(index);
}
return this->GetNextIndexHelper<Var>(index);
}
template<typename T>
uint32 JavascriptArray::GetNextIndexHelper(uint32 index) const
{
AssertMsg(this->head, "array head should never be null");
uint candidateIndex;
if (index == JavascriptArray::InvalidIndex)
{
candidateIndex = head->left;
}
else
{
candidateIndex = index + 1;
}
SparseArraySegment<T>* current = (SparseArraySegment<T>*)this->GetBeginLookupSegment(candidateIndex);
while (current != nullptr)
{
if ((current->left <= candidateIndex) && ((candidateIndex - current->left) < current->length))
{
for (uint i = candidateIndex - current->left; i < current->length; i++)
{
if (!SparseArraySegment<T>::IsMissingItem(&current->elements[i]))
{
return i + current->left;
}
}
}
current = SparseArraySegment<T>::From(current->next);
if (current != NULL)
{
if (candidateIndex < current->left)
{
candidateIndex = current->left;
}
}
}
return JavascriptArray::InvalidIndex;
}
// If new length > length, we just reset the length
// If new length < length, we need to remove the rest of the elements and segment
void JavascriptArray::SetLength(uint32 newLength)
{
if (newLength == length)
return;
if (head == EmptySegment)
{
// Do nothing to the segment.
}
else if (newLength == 0)
{
this->ClearElements(head, 0);
head->length = 0;
head->next = nullptr;
SetHasNoMissingValues();
ClearSegmentMap();
this->InvalidateLastUsedSegment();
}
else if (newLength < length)
{
// _ _ 2 3 _ _ 6 7 _ _
// SetLength(0)
// 0 <= left -> set *prev = null
// SetLength(2)
// 2 <= left -> set *prev = null
// SetLength(3)
// 3 !<= left; 3 <= right -> truncate to length - 1
// SetLength(5)
// 5 <=
SparseArraySegmentBase* next = GetBeginLookupSegment(newLength - 1); // head, or next.left < newLength
Field(SparseArraySegmentBase*)* prev = &head;
while(next != nullptr)
{
if (newLength <= next->left)
{
ClearSegmentMap(); // truncate segments, null out segmentMap
*prev = nullptr;
break;
}
else if (newLength <= (next->left + next->length))
{
if (next->next)
{
ClearSegmentMap(); // Will truncate segments, null out segmentMap
}
uint32 newSegmentLength = newLength - next->left;
this->ClearElements(next, newSegmentLength);
next->next = nullptr;
next->length = newSegmentLength;
next->CheckLengthvsSize();
break;
}
else
{
prev = &next->next;
next = next->next;
}
}
this->InvalidateLastUsedSegment();
}
this->length = newLength;
#ifdef VALIDATE_ARRAY
ValidateArray();
#endif
}
BOOL JavascriptArray::SetLength(Var newLength)
{
ScriptContext *scriptContext;
if(TaggedInt::Is(newLength))
{
int32 lenValue = TaggedInt::ToInt32(newLength);
if (lenValue < 0)
{
scriptContext = GetScriptContext();
if (scriptContext->GetThreadContext()->RecordImplicitException())
{
JavascriptError::ThrowRangeError(scriptContext, JSERR_ArrayLengthAssignIncorrect);
}
}
else
{
this->SetLength(lenValue);
}
return TRUE;
}
scriptContext = GetScriptContext();
uint32 uintValue = JavascriptConversion::ToUInt32(newLength, scriptContext);
double dblValue = JavascriptConversion::ToNumber(newLength, scriptContext);
if (dblValue == uintValue)
{
this->SetLength(uintValue);
}
else
{
ThreadContext* threadContext = scriptContext->GetThreadContext();
ImplicitCallFlags flags = threadContext->GetImplicitCallFlags();
if (flags != ImplicitCall_None && threadContext->IsDisableImplicitCall())
{
// We couldn't execute the implicit call(s) needed to convert the newLength to an integer.
// Do nothing and let the jitted code bail out.
return TRUE;
}
if (threadContext->RecordImplicitException())
{
JavascriptError::ThrowRangeError(scriptContext, JSERR_ArrayLengthAssignIncorrect);
}
}
return TRUE;
}
void JavascriptArray::ClearElements(SparseArraySegmentBase *seg, uint32 newSegmentLength)
{
SparseArraySegment<Var>::ClearElements(((SparseArraySegment<Var>*)seg)->elements + newSegmentLength, seg->length - newSegmentLength);
}
void JavascriptNativeIntArray::ClearElements(SparseArraySegmentBase *seg, uint32 newSegmentLength)
{
SparseArraySegment<int32>::ClearElements(((SparseArraySegment<int32>*)seg)->elements + newSegmentLength, seg->length - newSegmentLength);
}
void JavascriptNativeFloatArray::ClearElements(SparseArraySegmentBase *seg, uint32 newSegmentLength)
{
SparseArraySegment<double>::ClearElements(((SparseArraySegment<double>*)seg)->elements + newSegmentLength, seg->length - newSegmentLength);
}
Var JavascriptArray::DirectGetItem(uint32 index)
{
SparseArraySegment<Var> *seg = (SparseArraySegment<Var>*)this->GetLastUsedSegment();
uint32 offset = index - seg->left;
if (index >= seg->left && offset < seg->length)
{
if (!SparseArraySegment<Var>::IsMissingItem(&seg->elements[offset]))
{
return seg->elements[offset];
}
}
Var element = nullptr;
if (DirectGetItemAtFull(index, &element))
{
return element;
}
return GetType()->GetLibrary()->GetUndefined();
}
Var JavascriptNativeIntArray::DirectGetItem(uint32 index)
{
#if ENABLE_COPYONACCESS_ARRAY
JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(this);
#endif
SparseArraySegment<int32> *seg = (SparseArraySegment<int32>*)this->GetLastUsedSegment();
uint32 offset = index - seg->left;
if (index >= seg->left && offset < seg->length)
{
if (!SparseArraySegment<int32>::IsMissingItem(&seg->elements[offset]))
{
return JavascriptNumber::ToVar(seg->elements[offset], GetScriptContext());
}
}
Var element = nullptr;
if (DirectGetItemAtFull(index, &element))
{
return element;
}
return GetType()->GetLibrary()->GetUndefined();
}
DescriptorFlags JavascriptNativeIntArray::GetItemSetter(uint32 index, Var* setterValue, ScriptContext* requestContext)
{
#if ENABLE_COPYONACCESS_ARRAY
JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(this);
#endif
int32 value = 0;
return this->DirectGetItemAt(index, &value) ? WritableData : None;
}
Var JavascriptNativeFloatArray::DirectGetItem(uint32 index)
{
SparseArraySegment<double> *seg = (SparseArraySegment<double>*)this->GetLastUsedSegment();
uint32 offset = index - seg->left;
if (index >= seg->left && offset < seg->length)
{
if (!SparseArraySegment<double>::IsMissingItem(&seg->elements[offset]))
{
return JavascriptNumber::ToVarWithCheck(seg->elements[offset], GetScriptContext());
}
}
Var element = nullptr;
if (DirectGetItemAtFull(index, &element))
{
return element;
}
return GetType()->GetLibrary()->GetUndefined();
}
Var JavascriptArray::DirectGetItem(JavascriptString *propName, ScriptContext* scriptContext)
{
PropertyRecord const * propertyRecord;
scriptContext->GetOrAddPropertyRecord(propName, &propertyRecord);
return JavascriptOperators::GetProperty(this, propertyRecord->GetPropertyId(), scriptContext, NULL);
}
BOOL JavascriptArray::DirectGetItemAtFull(uint32 index, Var* outVal)
{
if (this->DirectGetItemAt(index, outVal))
{
return TRUE;
}
ScriptContext* requestContext = type->GetScriptContext();
return JavascriptOperators::GetItem(this, this->GetPrototype(), index, outVal, requestContext);
}
//
// Link prev and current. If prev is NULL, make current the head segment.
//
void JavascriptArray::LinkSegmentsCommon(SparseArraySegmentBase* prev, SparseArraySegmentBase* current)
{
if (prev)
{
prev->next = current;
}
else
{
Assert(current);
head = current;
}
}
template<typename T>
BOOL JavascriptArray::DirectDeleteItemAt(uint32 itemIndex)
{
if (itemIndex >= length)
{
return true;
}
SparseArraySegment<T>* next = (SparseArraySegment<T>*)GetBeginLookupSegment(itemIndex);
while(next != nullptr && next->left <= itemIndex)
{
uint32 limit = next->left + next->length;
if (itemIndex < limit)
{
next->SetElement(GetRecycler(), itemIndex, SparseArraySegment<T>::GetMissingItem());
if(itemIndex - next->left == next->length - 1)
{
--next->length;
next->CheckLengthvsSize();
}
else if(next == head)
{
SetHasNoMissingValues(false);
}
break;
}
next = SparseArraySegment<T>::From(next->next);
}
#ifdef VALIDATE_ARRAY
ValidateArray();
#endif
return true;
}
template <> Var JavascriptArray::ConvertToIndex(BigIndex idxDest, ScriptContext* scriptContext)
{
return idxDest.ToNumber(scriptContext);
}
template <> uint32 JavascriptArray::ConvertToIndex(BigIndex idxDest, ScriptContext* scriptContext)
{
// Note this is only for setting Array length which is a uint32
return idxDest.IsSmallIndex() ? idxDest.GetSmallIndex() : UINT_MAX;
}
template <> Var JavascriptArray::ConvertToIndex(uint32 idxDest, ScriptContext* scriptContext)
{
return JavascriptNumber::ToVar(idxDest, scriptContext);
}
void JavascriptArray::ThrowErrorOnFailure(BOOL succeeded, ScriptContext* scriptContext, uint32 index)
{
if (!succeeded)
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_CantRedefineProp, JavascriptConversion::ToString(JavascriptNumber::ToVar(index, scriptContext), scriptContext)->GetSz());
}
}
void JavascriptArray::ThrowErrorOnFailure(BOOL succeeded, ScriptContext* scriptContext, BigIndex index)
{
if (!succeeded)
{
uint64 i = (uint64)(index.IsSmallIndex() ? index.GetSmallIndex() : index.GetBigIndex());
JavascriptError::ThrowTypeError(scriptContext, JSERR_CantRedefineProp, JavascriptConversion::ToString(JavascriptNumber::ToVar(i, scriptContext), scriptContext)->GetSz());
}
}
void JavascriptArray::CreateDataPropertyOrThrow(RecyclableObject * obj, BigIndex index, Var item, ScriptContext * scriptContext)
{
JS_REENTRANCY_LOCK(jsReentLock, scriptContext->GetThreadContext());
JavascriptArray * arr = JavascriptArray::TryVarToNonES5Array(obj);
if (arr != nullptr)
{
arr->GenericDirectSetItemAt(index, item);
}
else
{
JS_REENTRANT(jsReentLock, ThrowErrorOnFailure(SetArrayLikeObjects(obj, index, item), scriptContext, index));
}
}
BOOL JavascriptArray::SetArrayLikeObjects(RecyclableObject* pDestObj, uint32 idxDest, Var aItem)
{
return pDestObj->SetItem(idxDest, aItem, Js::PropertyOperation_ThrowIfNotExtensible);
}
uint64 JavascriptArray::OP_GetLength(Var obj, ScriptContext *scriptContext)
{
if (scriptContext->GetConfig()->IsES6ToLengthEnabled())
{
// Casting to uint64 is okay as ToLength will always be >= 0.
return (uint64)JavascriptConversion::ToLength(JavascriptOperators::OP_GetLength(obj, scriptContext), scriptContext);
}
else
{
return (uint64)JavascriptConversion::ToUInt32(JavascriptOperators::OP_GetLength(obj, scriptContext), scriptContext);
}
}
template<typename T>
void JavascriptArray::TryGetArrayAndLength(Var arg,
ScriptContext *scriptContext,
PCWSTR methodName,
__out JavascriptArray** array,
__out RecyclableObject** obj,
__out T * length)
{
Assert(array != nullptr);
Assert(obj != nullptr);
Assert(length != nullptr);
*array = JavascriptArray::TryVarToNonES5Array(arg);
if (*array && !(*array)->IsCrossSiteObject())
{
#if ENABLE_COPYONACCESS_ARRAY
JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(*array);
#endif
*obj = *array;
*length = (*array)->length;
}
else
{
if (!JavascriptConversion::ToObject(arg, scriptContext, obj))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, methodName);
}
*length = OP_GetLength(*obj, scriptContext);
*array = nullptr;
}
}
BOOL JavascriptArray::SetArrayLikeObjects(RecyclableObject* pDestObj, BigIndex idxDest, Var aItem)
{
ScriptContext* scriptContext = pDestObj->GetScriptContext();
PropertyRecord const * propertyRecord;
if (idxDest.IsSmallIndex())
{
JavascriptOperators::GetPropertyIdForInt(idxDest.GetSmallIndex(), scriptContext, &propertyRecord);
}
else
{
JavascriptOperators::GetPropertyIdForInt(idxDest.GetBigIndex(), scriptContext, &propertyRecord);
}
PropertyDescriptor propertyDescriptor;
propertyDescriptor.SetConfigurable(true);
propertyDescriptor.SetEnumerable(true);
propertyDescriptor.SetWritable(true);
propertyDescriptor.SetValue(aItem);
return JavascriptObject::DefineOwnPropertyHelper(pDestObj, propertyRecord->GetPropertyId(), propertyDescriptor, scriptContext, false);
}
template<typename T>
void JavascriptArray::ConcatArgs(RecyclableObject* pDestObj, TypeId* remoteTypeIds,
Js::Arguments& args, ScriptContext* scriptContext, uint start, BigIndex startIdxDest,
ConcatSpreadableState previousItemSpreadableState /*= ConcatSpreadableState_NotChecked*/, BigIndex *firstPromotedItemLength /* = nullptr */)
{
// This never gets called.
Throw::InternalError();
}
//
// Helper for EntryConcat. Concat args or elements of arg arrays into dest array.
//
template<typename T>
void JavascriptArray::ConcatArgs(RecyclableObject* pDestObj, TypeId* remoteTypeIds,
Js::Arguments& args, ScriptContext* scriptContext, uint start, uint startIdxDest,
ConcatSpreadableState previousItemSpreadableState /*= ConcatSpreadableState_NotChecked*/, BigIndex *firstPromotedItemLength /* = nullptr */)
{
JS_REENTRANCY_LOCK(jsReentLock, scriptContext->GetThreadContext());
JavascriptArray* pDestArray = JavascriptArray::TryVarToNonES5Array(pDestObj);
if (pDestArray)
{
// ConcatArgs function expects to work on the Var array so we are ensuring it.
pDestArray = EnsureNonNativeArray(pDestArray);
SET_SECOND_OBJECT_FOR_MUTATION(jsReentLock, pDestArray);
}
AssertOrFailFast(scriptContext->GetConfig()->IsES6IsConcatSpreadableEnabled()); // ConcatSpreadable is enabled already - not going back.
T idxDest = startIdxDest;
for (uint idxArg = start; idxArg < args.Info.Count; idxArg++)
{
Var aItem = args[idxArg];
SETOBJECT_FOR_MUTATION(jsReentLock, aItem);
bool spreadable = previousItemSpreadableState == ConcatSpreadableState_CheckedAndTrue;
if (previousItemSpreadableState == ConcatSpreadableState_NotChecked)
{
JS_REENTRANT(jsReentLock, spreadable = !!JavascriptOperators::IsConcatSpreadable(aItem));
}
// Reset the state for the next item in the array
previousItemSpreadableState = ConcatSpreadableState_NotChecked;
if (!spreadable)
{
JS_REENTRANT(jsReentLock, JavascriptArray::SetConcatItem<T>(aItem, idxArg, pDestArray, pDestObj, idxDest, scriptContext));
++idxDest;
continue;
}
if (pDestArray && JavascriptArray::IsDirectAccessArray(aItem) && JavascriptArray::IsDirectAccessArray(pDestArray)
&& BigIndex(idxDest + UnsafeVarTo<JavascriptArray>(aItem)->length).IsSmallIndex() && !UnsafeVarTo<JavascriptArray>(aItem)->IsFillFromPrototypes()) // Fast path
{
JavascriptNativeIntArray *pIntItemArray = JavascriptOperators::TryFromVar<JavascriptNativeIntArray>(aItem);
if (pIntItemArray)
{
JS_REENTRANT_NO_MUTATE(jsReentLock, CopyNativeIntArrayElementsToVar(pDestArray, BigIndex(idxDest).GetSmallIndex(), pIntItemArray));
idxDest = idxDest + pIntItemArray->length;
}
else
{
JavascriptNativeFloatArray *pFloatItemArray = JavascriptOperators::TryFromVar<JavascriptNativeFloatArray>(aItem);
if (pFloatItemArray)
{
JS_REENTRANT_NO_MUTATE(jsReentLock, CopyNativeFloatArrayElementsToVar(pDestArray, BigIndex(idxDest).GetSmallIndex(), pFloatItemArray));
idxDest = idxDest + pFloatItemArray->length;
}
else
{
JavascriptArray* pItemArray = UnsafeVarTo<JavascriptArray>(aItem);
JS_REENTRANT(jsReentLock, CopyArrayElements(pDestArray, BigIndex(idxDest).GetSmallIndex(), pItemArray));
idxDest = idxDest + pItemArray->length;
}
}
}
else
{
AssertOrFailFast(VarIs<RecyclableObject>(aItem));
//CONSIDER: enumerating remote array instead of walking all indices
BigIndex length;
if (firstPromotedItemLength != nullptr)
{
length = *firstPromotedItemLength;
}
else
{
JS_REENTRANT(jsReentLock, length = OP_GetLength(aItem, scriptContext));
}
if (PromoteToBigIndex(length, idxDest))
{
// This is a special case for spreadable objects. We do not pre-calculate the length
// in EntryConcat like we do with Arrays because a getProperty on an object Length
// is observable. The result is we have to check for overflows separately for
// spreadable objects and promote to a bigger index type when we find them.
JS_REENTRANT(jsReentLock, ConcatArgs<BigIndex>(pDestObj, remoteTypeIds, args, scriptContext, idxArg, idxDest, ConcatSpreadableState_CheckedAndTrue, &length));
return;
}
if (length + idxDest > FiftyThirdPowerOfTwoMinusOne) // 2^53-1: from ECMA 22.1.3.1 Array.prototype.concat(...arguments)
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_IllegalArraySizeAndLength);
}
RecyclableObject* itemObject = VarTo<RecyclableObject>(aItem);
Var subItem;
uint32 lengthToUin32Max = length.IsSmallIndex() ? length.GetSmallIndex() : MaxArrayLength;
for (uint32 idxSubItem = 0u; idxSubItem < lengthToUin32Max; ++idxSubItem)
{
JS_REENTRANT(jsReentLock, BOOL hasItem = JavascriptOperators::HasItem(itemObject, idxSubItem));
if (hasItem)
{
JS_REENTRANT(jsReentLock, subItem = JavascriptOperators::GetItem(itemObject, idxSubItem, scriptContext));
if (pDestArray)
{
pDestArray->GenericDirectSetItemAt(idxDest, subItem);
}
else
{
JS_REENTRANT(jsReentLock, ThrowErrorOnFailure(SetArrayLikeObjects(pDestObj, idxDest, subItem), scriptContext, idxDest));
}
}
++idxDest;
}
for (BigIndex idxSubItem = MaxArrayLength; idxSubItem < length; ++idxSubItem)
{
PropertyRecord const * propertyRecord;
JavascriptOperators::GetPropertyIdForInt(idxSubItem.GetBigIndex(), scriptContext, &propertyRecord);
JS_REENTRANT(jsReentLock, BOOL hasProp = JavascriptOperators::HasProperty(itemObject, propertyRecord->GetPropertyId()));
if (hasProp)
{
JS_REENTRANT(jsReentLock, subItem = JavascriptOperators::GetProperty(itemObject, propertyRecord->GetPropertyId(), scriptContext));
if (pDestArray)
{
pDestArray->GenericDirectSetItemAt(idxDest, subItem);
}
else
{
JS_REENTRANT(jsReentLock, ThrowErrorOnFailure(SetArrayLikeObjects(pDestObj, idxDest, subItem), scriptContext, idxSubItem));
}
}
++idxDest;
}
}
firstPromotedItemLength = nullptr;
}
if (!pDestArray)
{
JS_REENTRANT(jsReentLock, pDestObj->SetProperty(PropertyIds::length, ConvertToIndex<T, Var>(idxDest, scriptContext), Js::PropertyOperation_None, nullptr));
}
else if (pDestArray->GetLength() != ConvertToIndex<T, uint32>(idxDest, scriptContext))
{
pDestArray->SetLength(ConvertToIndex<T, uint32>(idxDest, scriptContext));
}
}
bool JavascriptArray::PromoteToBigIndex(BigIndex lhs, BigIndex rhs)
{
return false; // already a big index
}
bool JavascriptArray::PromoteToBigIndex(BigIndex lhs, uint32 rhs)
{
::Math::RecordOverflowPolicy destLengthOverflow;
if (lhs.IsSmallIndex())
{
UInt32Math::Add(lhs.GetSmallIndex(), rhs, destLengthOverflow);
return destLengthOverflow.HasOverflowed();
}
return true;
}
JavascriptArray* JavascriptArray::ConcatIntArgs(JavascriptNativeIntArray* pDestArray, TypeId *remoteTypeIds, Js::Arguments& args, ScriptContext* scriptContext)
{
JS_REENTRANCY_LOCK(jsReentLock, scriptContext->GetThreadContext());
SET_SECOND_OBJECT_FOR_MUTATION(jsReentLock, pDestArray);
AssertOrFailFast(scriptContext->GetConfig()->IsES6IsConcatSpreadableEnabled());
Assert(pDestArray->GetTypeId() == TypeIds_NativeIntArray);
uint idxDest = 0u;
for (uint idxArg = 0; idxArg < args.Info.Count; idxArg++)
{
Var aItem = args[idxArg];
SETOBJECT_FOR_MUTATION(jsReentLock, aItem);
bool spreadable = false;
JS_REENTRANT(jsReentLock, spreadable = !!JavascriptOperators::IsConcatSpreadable(aItem));
if (!VarIsCorrectType(pDestArray))
{
JS_REENTRANT(jsReentLock, ConcatArgs<uint>(pDestArray, remoteTypeIds, args, scriptContext, idxArg, idxDest,
spreadable ? ConcatSpreadableState_CheckedAndTrue : ConcatSpreadableState_CheckedAndFalse));
return pDestArray;
}
if (!spreadable)
{
JS_REENTRANT(jsReentLock, pDestArray->SetItem(idxDest, aItem, PropertyOperation_ThrowIfNotExtensible));
idxDest++;
if (!VarIsCorrectType(pDestArray)) // SetItem could convert pDestArray to a var array if aItem is not an integer if so fall back
{
JS_REENTRANT(jsReentLock, ConcatArgs<uint>(pDestArray, remoteTypeIds, args, scriptContext, idxArg + 1, idxDest, ConcatSpreadableState_NotChecked));
return pDestArray;
}
continue;
}
JavascriptNativeIntArray * pItemArray = JavascriptOperators::TryFromVar<JavascriptNativeIntArray>(aItem);
if (pItemArray && !pItemArray->IsFillFromPrototypes()) // Fast path
{
JS_REENTRANT_NO_MUTATE(jsReentLock, bool converted = CopyNativeIntArrayElements(pDestArray, idxDest, pItemArray));
idxDest = idxDest + pItemArray->length;
if (converted)
{
// Copying the last array forced a conversion, so switch over to the var version
// to finish.
JS_REENTRANT(jsReentLock, ConcatArgs<uint>(pDestArray, remoteTypeIds, args, scriptContext, idxArg + 1, idxDest, ConcatSpreadableState_NotChecked));
return pDestArray;
}
}
else if (!JavascriptArray::IsAnyArray(aItem) && remoteTypeIds[idxArg] != TypeIds_Array)
{
if (TaggedInt::Is(aItem))
{
int32 int32Value = TaggedInt::ToInt32(aItem);
Assert(!SparseArraySegment<int32>::IsMissingItem(&int32Value));
pDestArray->DirectSetItemAt(idxDest, int32Value);
}
else
{
pDestArray->DirectSetItemAt(idxDest, static_cast<int32>(JavascriptNumber::GetValue(aItem)));
}
++idxDest;
}
else
{
JavascriptArray *pVarDestArray = JavascriptNativeIntArray::ConvertToVarArray(pDestArray);
BigIndex length;
JS_REENTRANT(jsReentLock, length = OP_GetLength(aItem, scriptContext),
ConcatArgs<uint>(pVarDestArray, remoteTypeIds, args, scriptContext, idxArg, idxDest, ConcatSpreadableState_CheckedAndTrue, &length));
return pVarDestArray;
}
}
if (pDestArray->GetLength() != idxDest)
{
pDestArray->SetLength(idxDest);
}
return pDestArray;
}
JavascriptArray* JavascriptArray::ConcatFloatArgs(JavascriptNativeFloatArray* pDestArray, TypeId *remoteTypeIds, Js::Arguments& args, ScriptContext* scriptContext)
{
JS_REENTRANCY_LOCK(jsReentLock, scriptContext->GetThreadContext());
SET_SECOND_OBJECT_FOR_MUTATION(jsReentLock, pDestArray);
AssertOrFailFast(scriptContext->GetConfig()->IsES6IsConcatSpreadableEnabled());
uint idxDest = 0u;
for (uint idxArg = 0; idxArg < args.Info.Count; idxArg++)
{
Var aItem = args[idxArg];
SETOBJECT_FOR_MUTATION(jsReentLock, aItem);
bool spreadable = false;
JS_REENTRANT(jsReentLock, spreadable = !!JavascriptOperators::IsConcatSpreadable(aItem));
if (!VarIsCorrectType(pDestArray))
{
JS_REENTRANT(jsReentLock, ConcatArgs<uint>(pDestArray, remoteTypeIds, args, scriptContext, idxArg, idxDest,
spreadable ? ConcatSpreadableState_CheckedAndTrue : ConcatSpreadableState_CheckedAndFalse));
return pDestArray;
}
if (!spreadable)
{
JS_REENTRANT(jsReentLock, pDestArray->SetItem(idxDest, aItem, PropertyOperation_ThrowIfNotExtensible));
idxDest = idxDest + 1;
if (!VarIsCorrectType(pDestArray)) // SetItem could convert pDestArray to a var array if aItem is not an integer if so fall back
{
JS_REENTRANT(jsReentLock, ConcatArgs<uint>(pDestArray, remoteTypeIds, args, scriptContext, idxArg + 1, idxDest, ConcatSpreadableState_NotChecked));
return pDestArray;
}
continue;
}
bool converted = false;
if (JavascriptArray::IsAnyArray(aItem) || remoteTypeIds[idxArg] == TypeIds_Array)
{
bool isFillFromPrototypes = UnsafeVarTo<JavascriptArray>(aItem)->IsFillFromPrototypes();
JavascriptNativeIntArray * pIntItemArray = JavascriptOperators::TryFromVar<JavascriptNativeIntArray>(aItem);
if (pIntItemArray && !isFillFromPrototypes) // Fast path
{
JS_REENTRANT_NO_MUTATE(jsReentLock, converted = CopyNativeIntArrayElementsToFloat(pDestArray, idxDest, pIntItemArray));
idxDest = idxDest + pIntItemArray->length;
}
else
{
JavascriptNativeFloatArray * pFloatItemArray = JavascriptOperators::TryFromVar<JavascriptNativeFloatArray>(aItem);
if (pFloatItemArray && !isFillFromPrototypes)
{
JS_REENTRANT_NO_MUTATE(jsReentLock, converted = CopyNativeFloatArrayElements(pDestArray, idxDest, pFloatItemArray));
idxDest = idxDest + pFloatItemArray->length;
}
else
{
JavascriptArray *pVarDestArray = JavascriptNativeFloatArray::ConvertToVarArray(pDestArray);
BigIndex length;
JS_REENTRANT(jsReentLock, length = OP_GetLength(aItem, scriptContext),
ConcatArgs<uint>(pVarDestArray, remoteTypeIds, args, scriptContext, idxArg, idxDest, ConcatSpreadableState_CheckedAndTrue, &length));
return pVarDestArray;
}
}
if (converted)
{
// Copying the last array forced a conversion, so switch over to the var version
// to finish.
JS_REENTRANT(jsReentLock, ConcatArgs<uint>(pDestArray, remoteTypeIds, args, scriptContext, idxArg + 1, idxDest, ConcatSpreadableState_NotChecked));
return pDestArray;
}
}
else
{
if (TaggedInt::Is(aItem))
{
pDestArray->DirectSetItemAt(idxDest, (double)TaggedInt::ToInt32(aItem));
}
else
{
Assert(JavascriptNumber::Is(aItem));
pDestArray->DirectSetItemAt(idxDest, JavascriptNumber::GetValue(aItem));
}
++idxDest;
}
}
if (pDestArray->GetLength() != idxDest)
{
pDestArray->SetLength(idxDest);
}
return pDestArray;
}
bool JavascriptArray::BoxConcatItem(Var aItem, uint idxArg, ScriptContext *scriptContext)
{
return idxArg == 0 && !JavascriptOperators::IsObject(aItem);
}
Var JavascriptArray::EntryConcat(RecyclableObject* function, CallInfo callInfo, ...)
{
JIT_HELPER_REENTRANT_HEADER(Array_Concat);
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
JS_REENTRANCY_LOCK(jsReentLock, scriptContext->GetThreadContext());
Assert(!(callInfo.Flags & CallFlags_New));
if (args.Info.Count == 0)
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, _u("Array.prototype.concat"));
}
//
// Compute the destination ScriptArray size:
// - Each item, flattening only one level if a ScriptArray.
//
uint32 cDestLength = 0;
JavascriptArray * pDestArray = NULL;
PROBE_STACK_NO_DISPOSE(function->GetScriptContext(), Js::Constants::MinStackDefault + (args.Info.Count * sizeof(TypeId*)));
TypeId* remoteTypeIds = (TypeId*)_alloca(args.Info.Count * sizeof(TypeId*));
bool isInt = true;
bool isFloat = true;
::Math::RecordOverflowPolicy destLengthOverflow;
for (uint idxArg = 0; idxArg < args.Info.Count; idxArg++)
{
Var aItem = args[idxArg];
#if ENABLE_COPYONACCESS_ARRAY
JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(aItem);
#endif
if (DynamicObject::IsAnyArray(aItem)) // Get JavascriptArray or ES5Array length
{
JavascriptArray * pItemArray = JavascriptArray::FromAnyArray(aItem);
if (isFloat)
{
if (!VarIs<JavascriptNativeIntArray>(pItemArray))
{
isInt = false;
if (!VarIs<JavascriptNativeFloatArray>(pItemArray))
{
isFloat = false;
}
}
}
cDestLength = UInt32Math::Add(cDestLength, pItemArray->GetLength(), destLengthOverflow);
}
else // Get remote array or object length
{
// We already checked for types derived from JavascriptArray. These are types that should behave like array
// i.e. proxy to array and remote array.
JS_REENTRANT(jsReentLock, BOOL isArray = JavascriptOperators::IsArray(aItem));
if (isArray)
{
// Don't try to preserve nativeness of remote arrays. The extra complexity is probably not
// worth it.
isInt = false;
isFloat = false;
if (!VarIs<JavascriptProxy>(aItem))
{
if (scriptContext->GetConfig()->IsES6ToLengthEnabled())
{
JS_REENTRANT(jsReentLock,
int64 len = JavascriptConversion::ToLength(JavascriptOperators::OP_GetLength(aItem, scriptContext), scriptContext));
// clipping to MaxArrayLength will overflow when added to cDestLength which we catch below
cDestLength = UInt32Math::Add(cDestLength, len < MaxArrayLength ? (uint32)len : MaxArrayLength, destLengthOverflow);
}
else
{
JS_REENTRANT(jsReentLock,
uint len = JavascriptConversion::ToUInt32(JavascriptOperators::OP_GetLength(aItem, scriptContext), scriptContext));
cDestLength = UInt32Math::Add(cDestLength, len, destLengthOverflow);
}
}
remoteTypeIds[idxArg] = TypeIds_Array; // Mark remote array, no matter remote JavascriptArray or ES5Array.
}
else
{
if (isFloat)
{
if (BoxConcatItem(aItem, idxArg, scriptContext))
{
// A primitive will be boxed, so we have to create a var array for the result.
isInt = false;
isFloat = false;
}
else if (!TaggedInt::Is(aItem))
{
if (!JavascriptNumber::Is(aItem))
{
isInt = false;
isFloat = false;
}
else if (isInt)
{
int32 int32Value;
if(!JavascriptNumber::TryGetInt32Value(JavascriptNumber::GetValue(aItem), &int32Value) ||
SparseArraySegment<int32>::IsMissingItem(&int32Value))
{
isInt = false;
}
}
}
else if(isInt)
{
int32 int32Value = TaggedInt::ToInt32(aItem);
if(SparseArraySegment<int32>::IsMissingItem(&int32Value))
{
isInt = false;
}
}
}
remoteTypeIds[idxArg] = TypeIds_Limit;
cDestLength = UInt32Math::Add(cDestLength, 1, destLengthOverflow);
}
}
}
if (destLengthOverflow.HasOverflowed())
{
cDestLength = MaxArrayLength;
isInt = false;
isFloat = false;
}
//
// Create the destination array
//
RecyclableObject* pDestObj = nullptr;
bool isArray = false;
JS_REENTRANT_NO_MUTATE(jsReentLock, pDestObj = ArraySpeciesCreate(args[0], 0, scriptContext));
if (pDestObj)
{
#if ENABLE_COPYONACCESS_ARRAY
JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(pDestObj);
#endif
// Check the thing that species create made. If it's a native array that can't handle the source
// data, convert it. If it's a more conservative kind of array than the source data, indicate that
// so that the data will be converted on copy.
if (isInt)
{
if (VarIs<JavascriptNativeIntArray>(pDestObj))
{
isArray = true;
}
else
{
isInt = false;
isFloat = VarIs<JavascriptNativeFloatArray>(pDestObj);
isArray = JavascriptArray::IsNonES5Array(pDestObj);
}
}
else if (isFloat)
{
JavascriptNativeIntArray *nativeIntArray = JavascriptOperators::TryFromVar<JavascriptNativeIntArray>(pDestObj);
if (nativeIntArray)
{
JavascriptNativeIntArray::ToNativeFloatArray(nativeIntArray);
isArray = true;
}
else
{
isFloat = VarIs<JavascriptNativeFloatArray>(pDestObj);
isArray = JavascriptArray::IsNonES5Array(pDestObj);
}
}
else
{
JavascriptNativeIntArray *nativeIntArray = JavascriptOperators::TryFromVar<Js::JavascriptNativeIntArray>(pDestObj);
if (nativeIntArray)
{
JavascriptNativeIntArray::ToVarArray(nativeIntArray);
isArray = true;
}
else
{
JavascriptNativeFloatArray *nativeFloatArray = JavascriptOperators::TryFromVar<Js::JavascriptNativeFloatArray>(pDestObj);
if (nativeFloatArray)
{
JavascriptNativeFloatArray::ToVarArray(nativeFloatArray);
isArray = true;
}
else
{
isArray = JavascriptArray::IsNonES5Array(pDestObj);
}
}
}
}
if (pDestObj == nullptr || isArray)
{
if (isInt)
{
JavascriptNativeIntArray *pIntArray = isArray ? VarTo<JavascriptNativeIntArray>(pDestObj) : scriptContext->GetLibrary()->CreateNativeIntArray(cDestLength);
pIntArray->EnsureHead<int32>();
JS_REENTRANT(jsReentLock, pDestArray = ConcatIntArgs(pIntArray, remoteTypeIds, args, scriptContext));
}
else if (isFloat)
{
JavascriptNativeFloatArray *pFArray = isArray ? VarTo<JavascriptNativeFloatArray>(pDestObj) : scriptContext->GetLibrary()->CreateNativeFloatArray(cDestLength);
pFArray->EnsureHead<double>();
JS_REENTRANT(jsReentLock, pDestArray = ConcatFloatArgs(pFArray, remoteTypeIds, args, scriptContext));
}
else
{
pDestArray = isArray ? VarTo<JavascriptArray>(pDestObj) : scriptContext->GetLibrary()->CreateArray(cDestLength);
// if the constructor has changed then we no longer specialize for ints and floats
pDestArray->EnsureHead<Var>();
JS_REENTRANT(jsReentLock, ConcatArgsCallingHelper(pDestArray, remoteTypeIds, args, scriptContext, destLengthOverflow));
}
//
// Return the new array instance.
//
#ifdef VALIDATE_ARRAY
pDestArray->ValidateArray();
#endif
return pDestArray;
}
Assert(pDestObj);
JS_REENTRANT(jsReentLock, ConcatArgsCallingHelper(pDestObj, remoteTypeIds, args, scriptContext, destLengthOverflow));
return pDestObj;
JIT_HELPER_END(Array_Concat);