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