void JSString::visitChildren(JSCell* cell, SlotVisitor& visitor) { JSString* thisObject = jsCast<JSString*>(cell); Base::visitChildren(thisObject, visitor); MARK_LOG_MESSAGE1("[%u]: ", thisObject->length()); #if ENABLE(OBJECT_MARK_LOGGING) if (!thisObject->isRope()) { WTF::StringImpl* ourImpl = thisObject->m_value.impl(); if (ourImpl->is8Bit()) MARK_LOG_MESSAGE1("[8 %p]", ourImpl->characters8()); else MARK_LOG_MESSAGE1("[16 %p]", ourImpl->characters16()); } else MARK_LOG_MESSAGE0("[rope]: "); #endif if (thisObject->isRope()) static_cast<JSRopeString*>(thisObject)->visitFibers(visitor); else { StringImpl* impl = thisObject->m_value.impl(); ASSERT(impl); visitor.reportExtraMemoryUsage(impl->costDuringGC()); } }
void JSRopeString::resolveRopeSlowCase(UChar* buffer) const { UChar* position = buffer + m_length; // We will be working backwards over the rope. Vector<JSString*, 32, UnsafeVectorOverflow> workQueue; // These strings are kept alive by the parent rope, so using a Vector is OK. for (size_t i = 0; i < s_maxInternalRopeLength && m_fibers[i]; ++i) workQueue.append(m_fibers[i].get()); while (!workQueue.isEmpty()) { JSString* currentFiber = workQueue.last(); workQueue.removeLast(); if (currentFiber->isRope()) { JSRopeString* currentFiberAsRope = static_cast<JSRopeString*>(currentFiber); for (size_t i = 0; i < s_maxInternalRopeLength && currentFiberAsRope->m_fibers[i]; ++i) workQueue.append(currentFiberAsRope->m_fibers[i].get()); continue; } StringImpl* string = static_cast<StringImpl*>(currentFiber->m_value.impl()); unsigned length = string->length(); position -= length; if (string->is8Bit()) StringImpl::copyChars(position, string->characters8(), length); else StringImpl::copyChars(position, string->characters16(), length); } ASSERT(buffer == position); ASSERT(!isRope()); }
// Overview: These functions convert a JSString from holding a string in rope form // down to a simple String representation. It does so by building up the string // backwards, since we want to avoid recursion, we expect that the tree structure // representing the rope is likely imbalanced with more nodes down the left side // (since appending to the string is likely more common) - and as such resolving // in this fashion should minimize work queue size. (If we built the queue forwards // we would likely have to place all of the constituent StringImpls into the // Vector before performing any concatenation, but by working backwards we likely // only fill the queue with the number of substrings at any given level in a // rope-of-ropes.) void JSRopeString::resolveRopeSlowCase8(LChar* buffer) const { LChar* position = buffer + m_length; // We will be working backwards over the rope. Vector<JSString*, 32, UnsafeVectorOverflow> workQueue; // Putting strings into a Vector is only OK because there are no GC points in this method. for (size_t i = 0; i < s_maxInternalRopeLength && m_fibers[i]; ++i) { workQueue.append(m_fibers[i].get()); // Clearing here works only because there are no GC points in this method. m_fibers[i].clear(); } while (!workQueue.isEmpty()) { JSString* currentFiber = workQueue.last(); workQueue.removeLast(); if (currentFiber->isRope()) { JSRopeString* currentFiberAsRope = static_cast<JSRopeString*>(currentFiber); for (size_t i = 0; i < s_maxInternalRopeLength && currentFiberAsRope->m_fibers[i]; ++i) workQueue.append(currentFiberAsRope->m_fibers[i].get()); continue; } StringImpl* string = static_cast<StringImpl*>(currentFiber->m_value.impl()); unsigned length = string->length(); position -= length; StringImpl::copyChars(position, string->characters8(), length); } ASSERT(buffer == position); ASSERT(!isRope()); }
size_t JSString::estimatedSize(JSCell* cell) { JSString* thisObject = asString(cell); if (thisObject->isRope()) return Base::estimatedSize(cell); return Base::estimatedSize(cell) + thisObject->m_value.impl()->costDuringGC(); }
// Overview: These functions convert a JSString from holding a string in rope form // down to a simple String representation. It does so by building up the string // backwards, since we want to avoid recursion, we expect that the tree structure // representing the rope is likely imbalanced with more nodes down the left side // (since appending to the string is likely more common) - and as such resolving // in this fashion should minimize work queue size. (If we built the queue forwards // we would likely have to place all of the constituent StringImpls into the // Vector before performing any concatenation, but by working backwards we likely // only fill the queue with the number of substrings at any given level in a // rope-of-ropes.) void JSRopeString::resolveRopeSlowCase8(LChar* buffer) const { LChar* position = buffer + length(); // We will be working backwards over the rope. Vector<JSString*, 32, UnsafeVectorOverflow> workQueue; // Putting strings into a Vector is only OK because there are no GC points in this method. for (size_t i = 0; i < s_maxInternalRopeLength && fiber(i); ++i) workQueue.append(fiber(i).get()); while (!workQueue.isEmpty()) { JSString* currentFiber = workQueue.last(); workQueue.removeLast(); const LChar* characters; if (currentFiber->isRope()) { JSRopeString* currentFiberAsRope = static_cast<JSRopeString*>(currentFiber); if (!currentFiberAsRope->isSubstring()) { for (size_t i = 0; i < s_maxInternalRopeLength && currentFiberAsRope->fiber(i); ++i) workQueue.append(currentFiberAsRope->fiber(i).get()); continue; } ASSERT(!currentFiberAsRope->substringBase()->isRope()); characters = currentFiberAsRope->substringBase()->m_value.characters8() + currentFiberAsRope->substringOffset(); } else characters = currentFiber->m_value.characters8(); unsigned length = currentFiber->length(); position -= length; StringImpl::copyChars(position, characters, length); } ASSERT(buffer == position); }
void JSString::visitChildren(JSCell* cell, SlotVisitor& visitor) { JSString* thisObject = asString(cell); Base::visitChildren(thisObject, visitor); if (thisObject->isRope()) static_cast<JSRopeString*>(thisObject)->visitFibers(visitor); if (StringImpl* impl = thisObject->m_value.impl()) visitor.reportExtraMemoryVisited(impl->costDuringGC()); }
void JSString::visitChildren(JSCell* cell, SlotVisitor& visitor) { JSString* thisObject = jsCast<JSString*>(cell); Base::visitChildren(thisObject, visitor); if (thisObject->isRope()) static_cast<JSRopeString*>(thisObject)->visitFibers(visitor); else { StringImpl* impl = thisObject->m_value.impl(); ASSERT(impl); visitor.reportExtraMemoryUsage(thisObject, impl->costDuringGC()); } }
void JSValue::dumpInContextAssumingStructure( PrintStream& out, DumpContext* context, Structure* structure) const { if (!*this) out.print("<JSValue()>"); else if (isInt32()) out.printf("Int32: %d", asInt32()); else if (isDouble()) { #if USE(JSVALUE64) out.printf("Double: %lld, %lf", (long long)reinterpretDoubleToInt64(asDouble()), asDouble()); #else union { double asDouble; uint32_t asTwoInt32s[2]; } u; u.asDouble = asDouble(); out.printf("Double: %08x:%08x, %lf", u.asTwoInt32s[1], u.asTwoInt32s[0], asDouble()); #endif } else if (isCell()) { if (structure->classInfo()->isSubClassOf(JSString::info())) { JSString* string = jsCast<JSString*>(asCell()); out.print("String"); if (string->isRope()) out.print(" (rope)"); const StringImpl* impl = string->tryGetValueImpl(); if (impl) { if (impl->isAtomic()) out.print(" (atomic)"); if (impl->isAtomic()) out.print(" (identifier)"); if (impl->isSymbol()) out.print(" (symbol)"); } else out.print(" (unresolved)"); out.print(": ", impl); } else if (structure->classInfo()->isSubClassOf(Symbol::info())) out.print("Symbol: ", RawPointer(asCell())); else if (structure->classInfo()->isSubClassOf(Structure::info())) out.print("Structure: ", inContext(*jsCast<Structure*>(asCell()), context)); else if (structure->classInfo()->isSubClassOf(JSObject::info())) { out.print("Object: ", RawPointer(asCell())); out.print(" with butterfly ", RawPointer(asObject(asCell())->butterfly())); out.print(" (", inContext(*structure, context), ")"); } else { out.print("Cell: ", RawPointer(asCell())); out.print(" (", inContext(*structure, context), ")"); } #if USE(JSVALUE64) out.print(", ID: ", asCell()->structureID()); #endif } else if (isTrue()) out.print("True"); else if (isFalse()) out.print("False"); else if (isNull()) out.print("Null"); else if (isUndefined()) out.print("Undefined"); else out.print("INVALID"); }
JSFlatString * JSRope::flattenInternal(ExclusiveContext *maybecx) { /* * Perform a depth-first dag traversal, splatting each node's characters * into a contiguous buffer. Visit each rope node three times: * 1. record position in the buffer and recurse into left child; * 2. recurse into the right child; * 3. transform the node into a dependent string. * To avoid maintaining a stack, tree nodes are mutated to indicate how many * times they have been visited. Since ropes can be dags, a node may be * encountered multiple times during traversal. However, step 3 above leaves * a valid dependent string, so everything works out. * * While ropes avoid all sorts of quadratic cases with string * concatenation, they can't help when ropes are immediately flattened. * One idiomatic case that we'd like to keep linear (and has traditionally * been linear in SM and other JS engines) is: * * while (...) { * s += ... * s.flatten * } * * To do this, when the buffer for a to-be-flattened rope is allocated, the * allocation size is rounded up. Then, if the resulting flat string is the * left-hand side of a new rope that gets flattened and there is enough * capacity, the rope is flattened into the same buffer, thereby avoiding * copying the left-hand side. Clearing the 'extensible' bit turns off this * optimization. This is necessary, e.g., when the JSAPI hands out the raw * null-terminated char array of a flat string. * * N.B. This optimization can create chains of dependent strings. */ const size_t wholeLength = length(); size_t wholeCapacity; CharT *wholeChars; JSString *str = this; CharT *pos; /* * JSString::flattenData is a tagged pointer to the parent node. * The tag indicates what to do when we return to the parent. */ static const uintptr_t Tag_Mask = 0x3; static const uintptr_t Tag_FinishNode = 0x0; static const uintptr_t Tag_VisitRightChild = 0x1; AutoCheckCannotGC nogc; /* Find the left most string, containing the first string. */ JSRope *leftMostRope = this; while (leftMostRope->leftChild()->isRope()) leftMostRope = &leftMostRope->leftChild()->asRope(); if (leftMostRope->leftChild()->isExtensible()) { JSExtensibleString &left = leftMostRope->leftChild()->asExtensible(); size_t capacity = left.capacity(); if (capacity >= wholeLength && left.hasTwoByteChars() == IsSame<CharT, jschar>::value) { /* * Simulate a left-most traversal from the root to leftMost->leftChild() * via first_visit_node */ JS_ASSERT(str->isRope()); while (str != leftMostRope) { if (b == WithIncrementalBarrier) { JSString::writeBarrierPre(str->d.s.u2.left); JSString::writeBarrierPre(str->d.s.u3.right); } JSString *child = str->d.s.u2.left; JS_ASSERT(child->isRope()); str->setNonInlineChars(left.nonInlineChars<CharT>(nogc)); child->d.u1.flattenData = uintptr_t(str) | Tag_VisitRightChild; str = child; } if (b == WithIncrementalBarrier) { JSString::writeBarrierPre(str->d.s.u2.left); JSString::writeBarrierPre(str->d.s.u3.right); } str->setNonInlineChars(left.nonInlineChars<CharT>(nogc)); wholeCapacity = capacity; wholeChars = const_cast<CharT *>(left.nonInlineChars<CharT>(nogc)); pos = wholeChars + left.d.u1.length; JS_STATIC_ASSERT(!(EXTENSIBLE_FLAGS & DEPENDENT_FLAGS)); left.d.u1.flags ^= (EXTENSIBLE_FLAGS | DEPENDENT_FLAGS); left.d.s.u3.base = (JSLinearString *)this; /* will be true on exit */ StringWriteBarrierPostRemove(maybecx, &left.d.s.u2.left); StringWriteBarrierPost(maybecx, (JSString **)&left.d.s.u3.base); goto visit_right_child; } } if (!AllocChars(maybecx, wholeLength, &wholeChars, &wholeCapacity)) return nullptr; pos = wholeChars; first_visit_node: { if (b == WithIncrementalBarrier) { JSString::writeBarrierPre(str->d.s.u2.left); JSString::writeBarrierPre(str->d.s.u3.right); } JSString &left = *str->d.s.u2.left; str->setNonInlineChars(pos); StringWriteBarrierPostRemove(maybecx, &str->d.s.u2.left); if (left.isRope()) { /* Return to this node when 'left' done, then goto visit_right_child. */ left.d.u1.flattenData = uintptr_t(str) | Tag_VisitRightChild; str = &left; goto first_visit_node; } CopyChars(pos, left.asLinear()); pos += left.length(); } visit_right_child: { JSString &right = *str->d.s.u3.right; if (right.isRope()) { /* Return to this node when 'right' done, then goto finish_node. */ right.d.u1.flattenData = uintptr_t(str) | Tag_FinishNode; str = &right; goto first_visit_node; } CopyChars(pos, right.asLinear()); pos += right.length(); } finish_node: { if (str == this) { JS_ASSERT(pos == wholeChars + wholeLength); *pos = '\0'; str->d.u1.length = wholeLength; if (IsSame<CharT, jschar>::value) str->d.u1.flags = EXTENSIBLE_FLAGS; else str->d.u1.flags = EXTENSIBLE_FLAGS | LATIN1_CHARS_BIT; str->setNonInlineChars(wholeChars); str->d.s.u3.capacity = wholeCapacity; StringWriteBarrierPostRemove(maybecx, &str->d.s.u2.left); StringWriteBarrierPostRemove(maybecx, &str->d.s.u3.right); return &this->asFlat(); } uintptr_t flattenData = str->d.u1.flattenData; if (IsSame<CharT, jschar>::value) str->d.u1.flags = DEPENDENT_FLAGS; else str->d.u1.flags = DEPENDENT_FLAGS | LATIN1_CHARS_BIT; str->d.u1.length = pos - str->asLinear().nonInlineChars<CharT>(nogc); str->d.s.u3.base = (JSLinearString *)this; /* will be true on exit */ StringWriteBarrierPost(maybecx, (JSString **)&str->d.s.u3.base); str = (JSString *)(flattenData & ~Tag_Mask); if ((flattenData & Tag_Mask) == Tag_VisitRightChild) goto visit_right_child; JS_ASSERT((flattenData & Tag_Mask) == Tag_FinishNode); goto finish_node; } }
JSFlatString* JSRope::flattenInternal(ExclusiveContext* maybecx) { /* * Consider the DAG of JSRopes rooted at this JSRope, with non-JSRopes as * its leaves. Mutate the root JSRope into a JSExtensibleString containing * the full flattened text that the root represents, and mutate all other * JSRopes in the interior of the DAG into JSDependentStrings that refer to * this new JSExtensibleString. * * If the leftmost leaf of our DAG is a JSExtensibleString, consider * stealing its buffer for use in our new root, and transforming it into a * JSDependentString too. Do not mutate any of the other leaves. * * Perform a depth-first dag traversal, splatting each node's characters * into a contiguous buffer. Visit each rope node three times: * 1. record position in the buffer and recurse into left child; * 2. recurse into the right child; * 3. transform the node into a dependent string. * To avoid maintaining a stack, tree nodes are mutated to indicate how many * times they have been visited. Since ropes can be dags, a node may be * encountered multiple times during traversal. However, step 3 above leaves * a valid dependent string, so everything works out. * * While ropes avoid all sorts of quadratic cases with string concatenation, * they can't help when ropes are immediately flattened. One idiomatic case * that we'd like to keep linear (and has traditionally been linear in SM * and other JS engines) is: * * while (...) { * s += ... * s.flatten * } * * Two behaviors accomplish this: * * - When the leftmost non-rope in the DAG we're flattening is a * JSExtensibleString with sufficient capacity to hold the entire * flattened string, we just flatten the DAG into its buffer. Then, when * we transform the root of the DAG from a JSRope into a * JSExtensibleString, we steal that buffer, and change the victim from a * JSExtensibleString to a JSDependentString. In this case, the left-hand * side of the string never needs to be copied. * * - Otherwise, we round up the total flattened size and create a fresh * JSExtensibleString with that much capacity. If this in turn becomes the * leftmost leaf of a subsequent flatten, we will hopefully be able to * fill it, as in the case above. * * Note that, even though the code for creating JSDependentStrings avoids * creating dependents of dependents, we can create that situation here: the * JSExtensibleStrings we transform into JSDependentStrings might have * JSDependentStrings pointing to them already. Stealing the buffer doesn't * change its address, only its owning JSExtensibleString, so all chars() * pointers in the JSDependentStrings are still valid. */ const size_t wholeLength = length(); size_t wholeCapacity; CharT* wholeChars; JSString* str = this; CharT* pos; /* * JSString::flattenData is a tagged pointer to the parent node. * The tag indicates what to do when we return to the parent. */ static const uintptr_t Tag_Mask = 0x3; static const uintptr_t Tag_FinishNode = 0x0; static const uintptr_t Tag_VisitRightChild = 0x1; AutoCheckCannotGC nogc; /* Find the left most string, containing the first string. */ JSRope* leftMostRope = this; while (leftMostRope->leftChild()->isRope()) leftMostRope = &leftMostRope->leftChild()->asRope(); if (leftMostRope->leftChild()->isExtensible()) { JSExtensibleString& left = leftMostRope->leftChild()->asExtensible(); size_t capacity = left.capacity(); if (capacity >= wholeLength && left.hasTwoByteChars() == IsSame<CharT, char16_t>::value) { /* * Simulate a left-most traversal from the root to leftMost->leftChild() * via first_visit_node */ MOZ_ASSERT(str->isRope()); while (str != leftMostRope) { if (b == WithIncrementalBarrier) { JSString::writeBarrierPre(str->d.s.u2.left); JSString::writeBarrierPre(str->d.s.u3.right); } JSString* child = str->d.s.u2.left; MOZ_ASSERT(child->isRope()); str->setNonInlineChars(left.nonInlineChars<CharT>(nogc)); child->d.u1.flattenData = uintptr_t(str) | Tag_VisitRightChild; str = child; } if (b == WithIncrementalBarrier) { JSString::writeBarrierPre(str->d.s.u2.left); JSString::writeBarrierPre(str->d.s.u3.right); } str->setNonInlineChars(left.nonInlineChars<CharT>(nogc)); wholeCapacity = capacity; wholeChars = const_cast<CharT*>(left.nonInlineChars<CharT>(nogc)); pos = wholeChars + left.d.u1.length; JS_STATIC_ASSERT(!(EXTENSIBLE_FLAGS & DEPENDENT_FLAGS)); left.d.u1.flags ^= (EXTENSIBLE_FLAGS | DEPENDENT_FLAGS); left.d.s.u3.base = (JSLinearString*)this; /* will be true on exit */ StringWriteBarrierPostRemove(maybecx, &left.d.s.u2.left); StringWriteBarrierPost(maybecx, (JSString**)&left.d.s.u3.base); goto visit_right_child; } } if (!AllocChars(this, wholeLength, &wholeChars, &wholeCapacity)) return nullptr; pos = wholeChars; first_visit_node: { if (b == WithIncrementalBarrier) { JSString::writeBarrierPre(str->d.s.u2.left); JSString::writeBarrierPre(str->d.s.u3.right); } JSString& left = *str->d.s.u2.left; str->setNonInlineChars(pos); StringWriteBarrierPostRemove(maybecx, &str->d.s.u2.left); if (left.isRope()) { /* Return to this node when 'left' done, then goto visit_right_child. */ left.d.u1.flattenData = uintptr_t(str) | Tag_VisitRightChild; str = &left; goto first_visit_node; } CopyChars(pos, left.asLinear()); pos += left.length(); } visit_right_child: { JSString& right = *str->d.s.u3.right; if (right.isRope()) { /* Return to this node when 'right' done, then goto finish_node. */ right.d.u1.flattenData = uintptr_t(str) | Tag_FinishNode; str = &right; goto first_visit_node; } CopyChars(pos, right.asLinear()); pos += right.length(); } finish_node: { if (str == this) { MOZ_ASSERT(pos == wholeChars + wholeLength); *pos = '\0'; str->d.u1.length = wholeLength; if (IsSame<CharT, char16_t>::value) str->d.u1.flags = EXTENSIBLE_FLAGS; else str->d.u1.flags = EXTENSIBLE_FLAGS | LATIN1_CHARS_BIT; str->setNonInlineChars(wholeChars); str->d.s.u3.capacity = wholeCapacity; StringWriteBarrierPostRemove(maybecx, &str->d.s.u2.left); StringWriteBarrierPostRemove(maybecx, &str->d.s.u3.right); return &this->asFlat(); } uintptr_t flattenData = str->d.u1.flattenData; if (IsSame<CharT, char16_t>::value) str->d.u1.flags = DEPENDENT_FLAGS; else str->d.u1.flags = DEPENDENT_FLAGS | LATIN1_CHARS_BIT; str->d.u1.length = pos - str->asLinear().nonInlineChars<CharT>(nogc); str->d.s.u3.base = (JSLinearString*)this; /* will be true on exit */ StringWriteBarrierPost(maybecx, (JSString**)&str->d.s.u3.base); str = (JSString*)(flattenData & ~Tag_Mask); if ((flattenData & Tag_Mask) == Tag_VisitRightChild) goto visit_right_child; MOZ_ASSERT((flattenData & Tag_Mask) == Tag_FinishNode); goto finish_node; } }