blob: 659023f39957aa6ae42bdfc74ae65af5c13d2e83 [file]
//-------------------------------------------------------------------------------------------------------
// 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"
namespace Js
{
DEFINE_RECYCLER_TRACKER_PERF_COUNTER(ConcatString);
// Note: see also: ConcatString.inl
LiteralStringWithPropertyStringPtr::LiteralStringWithPropertyStringPtr(StaticType* stringType) :
LiteralString(stringType),
propertyString(nullptr)
{
}
PropertyString * LiteralStringWithPropertyStringPtr::GetPropertyString() const
{
return this->propertyString;
}
void LiteralStringWithPropertyStringPtr::SetPropertyString(PropertyString * propStr)
{
this->propertyString = propStr;
}
/////////////////////// ConcatStringBase //////////////////////////
ConcatStringBase::ConcatStringBase(StaticType* stringType) : LiteralString(stringType)
{
}
// Copy the content of items into specified buffer.
void ConcatStringBase::CopyImpl(_Out_writes_(m_charLength) char16 *const buffer,
int itemCount, _In_reads_(itemCount) JavascriptString * const * items,
StringCopyInfoStack &nestedStringTreeCopyInfos, const byte recursionDepth)
{
Assert(!IsFinalized());
Assert(buffer);
CharCount copiedCharLength = 0;
for(int i = 0; i < itemCount; ++i)
{
JavascriptString *const s = items[i];
if(!s)
{
continue;
}
if (s->IsFinalized())
{
// If we have the buffer already, just copy it
const CharCount copyCharLength = s->GetLength();
AnalysisAssert(copiedCharLength + copyCharLength <= this->GetLength());
CopyHelper(&buffer[copiedCharLength], s->GetString(), copyCharLength);
copiedCharLength += copyCharLength;
continue;
}
if(i == itemCount - 1)
{
JavascriptString * const * newItems;
int newItemCount = s->GetRandomAccessItemsFromConcatString(newItems);
if (newItemCount != -1)
{
// Optimize for right-weighted ConcatString tree (the append case). Even though appending to a ConcatString will
// transition into a CompoundString fairly quickly, strings created by doing just a few appends are very common.
items = newItems;
itemCount = newItemCount;
i = -1;
continue;
}
}
const CharCount copyCharLength = s->GetLength();
AnalysisAssert(copyCharLength <= GetLength() - copiedCharLength);
if(recursionDepth == MaxCopyRecursionDepth && s->IsTree())
{
// Don't copy nested string trees yet, as that involves a recursive call, and the recursion can become
// excessive. Just collect the nested string trees and the buffer location where they should be copied, and
// the caller can deal with those after returning.
nestedStringTreeCopyInfos.Push(StringCopyInfo(s, &buffer[copiedCharLength]));
}
else
{
Assert(recursionDepth <= MaxCopyRecursionDepth);
s->Copy(&buffer[copiedCharLength], nestedStringTreeCopyInfos, recursionDepth + 1);
}
copiedCharLength += copyCharLength;
}
Assert(copiedCharLength == GetLength());
}
bool ConcatStringBase::IsTree() const
{
Assert(!IsFinalized());
return true;
}
/////////////////////// ConcatString //////////////////////////
ConcatString::ConcatString(JavascriptString* a, JavascriptString* b) :
ConcatStringN<2>(a->GetLibrary()->GetStringTypeStatic(), false)
{
Assert(a);
Assert(b);
a = CompoundString::GetImmutableOrScriptUnreferencedString(a);
b = CompoundString::GetImmutableOrScriptUnreferencedString(b);
m_slots[0] = a;
m_slots[1] = b;
this->SetLength(a->GetLength() + b->GetLength()); // does not include null character
}
ConcatString* ConcatString::New(JavascriptString* left, JavascriptString* right)
{
Assert(left);
#ifdef PROFILE_STRINGS
StringProfiler::RecordConcatenation( left->GetScriptContext(), left->GetLength(), right->GetLength(), ConcatType_ConcatTree);
#endif
Recycler* recycler = left->GetScriptContext()->GetRecycler();
return RecyclerNew(recycler, ConcatString, left, right);
}
/////////////////////// ConcatStringBuilder //////////////////////////
// MAX number of slots in one chunk. Until we fit into this, we realloc, otherwise create new chunk.
// The VS2013 linker treats this as a redefinition of an already
// defined constant and complains. So skip the declaration if we're compiling
// with VS2013 or below.
#if !defined(_MSC_VER) || _MSC_VER >= 1900
const int ConcatStringBuilder::c_maxChunkSlotCount;
#endif
ConcatStringBuilder::ConcatStringBuilder(ScriptContext* scriptContext, int initialSlotCount) :
ConcatStringBase(scriptContext->GetLibrary()->GetStringTypeStatic()),
m_count(0), m_prevChunk(nullptr)
{
Assert(scriptContext);
// Note: m_slotCount is a valid scenario -- when you don't know how many will be there.
this->AllocateSlots(initialSlotCount);
this->SetLength(0); // does not include null character
}
ConcatStringBuilder::ConcatStringBuilder(const ConcatStringBuilder& other):
ConcatStringBase(other.GetScriptContext()->GetLibrary()->GetStringTypeStatic())
{
m_slots = other.m_slots;
m_count = other.m_count;
m_slotCount = other.m_slotCount;
m_prevChunk = other.m_prevChunk;
this->SetLength(other.GetLength());
// TODO: should we copy the JavascriptString buffer and if so, how do we pass over the ownership?
}
ConcatStringBuilder* ConcatStringBuilder::New(ScriptContext* scriptContext, int initialSlotCount)
{
Assert(scriptContext);
return RecyclerNew(scriptContext->GetRecycler(), ConcatStringBuilder, scriptContext, initialSlotCount);
}
const char16 * ConcatStringBuilder::GetSz()
{
const char16 * sz = GetSzImpl<ConcatStringBuilder>();
// Allow a/b to be garbage collected if no more refs.
ConcatStringBuilder* current = this;
while (current != NULL)
{
ClearArray(current->m_slots, current->m_count);
current = current->m_prevChunk;
}
LiteralStringWithPropertyStringPtr::ConvertString(this);
return sz;
}
// Append/concat a new string to us.
// The idea is that we will grow/realloc current slot if new size fits into MAX chunk size (c_maxChunkSlotCount).
// Otherwise we will create a new chunk.
void ConcatStringBuilder::Append(JavascriptString* str)
{
// Note: we are quite lucky here because we always add 1 (no more) string to us.
Assert(str);
charcount_t len = this->GetLength(); // This is len of all chunks.
if (m_count == m_slotCount)
{
// Out of free slots, current chunk is full, need to grow.
int oldItemCount = this->GetItemCount();
int newItemCount = oldItemCount > 0 ?
oldItemCount > 1 ? oldItemCount + oldItemCount / 2 : 2 :
1;
Assert(newItemCount > oldItemCount);
int growDelta = newItemCount - oldItemCount; // # of items to grow by.
int newSlotCount = m_slotCount + growDelta;
if (newSlotCount <= c_maxChunkSlotCount)
{
// While we fit into MAX chunk size, realloc/grow current chunk.
Field(JavascriptString*)* newSlots = RecyclerNewArray(
this->GetScriptContext()->GetRecycler(), Field(JavascriptString*), newSlotCount);
CopyArray(newSlots, newSlotCount, m_slots, m_slotCount);
m_slots = newSlots;
m_slotCount = newSlotCount;
}
else
{
// Create new chunk with MAX size, swap new instance's data with this's data.
// We never create more than one chunk at a time.
ConcatStringBuilder* newChunk = RecyclerNew(this->GetScriptContext()->GetRecycler(), ConcatStringBuilder, *this); // Create a copy.
m_prevChunk = newChunk;
m_count = 0;
AllocateSlots(this->c_maxChunkSlotCount);
Assert(m_slots);
}
}
str = CompoundString::GetImmutableOrScriptUnreferencedString(str);
m_slots[m_count++] = str;
len += str->GetLength();
this->SetLength(len);
}
// Allocate slots, set m_slots and m_slotCount.
// Note: the amount of slots allocated can be less than the requestedSlotCount parameter.
void ConcatStringBuilder::AllocateSlots(int requestedSlotCount)
{
if (requestedSlotCount > 0)
{
m_slotCount = min(requestedSlotCount, this->c_maxChunkSlotCount);
m_slots = RecyclerNewArray(this->GetScriptContext()->GetRecycler(), Field(JavascriptString*), m_slotCount);
}
else
{
m_slotCount = 0;
m_slots = nullptr;
}
}
// Returns the number of JavascriptString* items accumulated so far in all chunks.
int ConcatStringBuilder::GetItemCount() const
{
int count = 0;
const ConcatStringBuilder* current = this;
while (current != NULL)
{
count += current->m_count;
current = current->m_prevChunk;
}
return count;
}
ConcatStringBuilder* ConcatStringBuilder::GetHead() const
{
ConcatStringBuilder* current = const_cast<ConcatStringBuilder*>(this);
ConcatStringBuilder* head;
do
{
head = current;
current = current->m_prevChunk;
} while (current != NULL);
return head;
}
void ConcatStringBuilder::CopyVirtual(
_Out_writes_(m_charLength) char16 *const buffer,
StringCopyInfoStack &nestedStringTreeCopyInfos,
const byte recursionDepth)
{
Assert(!this->IsFinalized());
Assert(buffer);
CharCount remainingCharLengthToCopy = GetLength();
for(const ConcatStringBuilder *current = this; current; current = current->m_prevChunk)
{
for(int i = current->m_count - 1; i >= 0; --i)
{
JavascriptString *const s = current->m_slots[i];
if(!s)
{
continue;
}
const CharCount copyCharLength = s->GetLength();
Assert(remainingCharLengthToCopy >= copyCharLength);
remainingCharLengthToCopy -= copyCharLength;
if(recursionDepth == MaxCopyRecursionDepth && s->IsTree())
{
// Don't copy nested string trees yet, as that involves a recursive call, and the recursion can become
// excessive. Just collect the nested string trees and the buffer location where they should be copied, and
// the caller can deal with those after returning.
nestedStringTreeCopyInfos.Push(StringCopyInfo(s, &buffer[remainingCharLengthToCopy]));
}
else
{
Assert(recursionDepth <= MaxCopyRecursionDepth);
s->Copy(&buffer[remainingCharLengthToCopy], nestedStringTreeCopyInfos, recursionDepth + 1);
}
}
}
}
/////////////////////// ConcatStringMulti //////////////////////////
ConcatStringMulti::ConcatStringMulti(uint slotCount, JavascriptString * a1, JavascriptString * a2, StaticType* stringTypeStatic) :
ConcatStringBase(stringTypeStatic), slotCount(slotCount)
{
#if DBG
ClearArray(m_slots, slotCount);
#endif
m_slots[0] = CompoundString::GetImmutableOrScriptUnreferencedString(a1);
m_slots[1] = CompoundString::GetImmutableOrScriptUnreferencedString(a2);
this->SetLength(a1->GetLength() + a2->GetLength());
}
size_t
ConcatStringMulti::GetAllocSize(uint slotCount)
{
return sizeof(ConcatStringMulti) + (sizeof(JavascriptString *) * slotCount);
}
ConcatStringMulti*
ConcatStringMulti::New(uint slotCount, JavascriptString * a1, JavascriptString * a2, ScriptContext * scriptContext)
{
return RecyclerNewPlus(scriptContext->GetRecycler(),
sizeof(JavascriptString *) * slotCount, ConcatStringMulti, slotCount, a1, a2,
scriptContext->GetLibrary()->GetStringTypeStatic());
}
bool
ConcatStringMulti::Is(Var var)
{
return VirtualTableInfo<ConcatStringMulti>::HasVirtualTable(var);
}
ConcatStringMulti *
ConcatStringMulti::FromVar(Var var)
{
Assert(ConcatStringMulti::Is(var));
return static_cast<ConcatStringMulti *>(var);
}
const char16 *
ConcatStringMulti::GetSz()
{
Assert(IsFilled());
const char16 * sz = GetSzImpl<ConcatStringMulti>();
// Allow slots to be garbage collected if no more refs.
ClearArray(m_slots, slotCount);
LiteralStringWithPropertyStringPtr::ConvertString(this);
return sz;
}
void
ConcatStringMulti::SetItem(_In_range_(0, slotCount - 1) uint index, JavascriptString* value)
{
Assert(index < slotCount);
Assert(m_slots[index] == nullptr);
value = CompoundString::GetImmutableOrScriptUnreferencedString(value);
this->SetLength(this->GetLength() + value->GetLength());
m_slots[index] = value;
}
#if DBG
bool
ConcatStringMulti::IsFilled() const
{
for (uint i = slotCount; i > 0; i--)
{
if (m_slots[i - 1] == nullptr) { return false; }
}
return true;
}
#endif
} // namespace Js.