blob: 7ac7247eeaf746eebd68efa7e7ed763758fe5881 [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// 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());
JavascriptNativeIntArray *arr = scriptContext->GetLibrary()->CreateNativeIntArrayLiteral(ints->count);
SparseArraySegment<int32> * segment = (SparseArraySegment<int32>*)arr->GetHead();
JavascriptOperators::AddIntsToArraySegment(segment, ints);
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;
}
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_ProfiledNewScIntArray);
}
#endif
Var JavascriptArray::OP_NewScFltArray(AuxArray<double> *doubles, ScriptContext* scriptContext)
{
JIT_HELPER_NOT_REENTRANT_HEADER(ScrArr_OP_NewScFltArray, reentrancylock, scriptContext->GetThreadContext());
JavascriptNativeFloatArray *arr = scriptContext->GetLibrary()->CreateNativeFloatArrayLiteral(doubles->count);
SparseArraySegment<double> * segment = (SparseArraySegment<double>*)arr->GetHead();
JavascriptOperators::AddFloatsToArraySegment(segment, doubles);
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;
}
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_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