Bug 609440, part 2 - do lazy allocation, dag-ify ropes (assume no oom) (r=njn)

This commit is contained in:
Luke Wagner 2010-11-30 18:41:32 -08:00
parent d9a9bbca84
commit b53039418b
13 changed files with 551 additions and 653 deletions

View File

@ -514,7 +514,7 @@ js_AtomizeString(JSContext *cx, JSString *str, uintN flags)
/* Finish handing off chars to the GC'ed key string. */
JS_ASSERT(flags & ATOM_TMPSTR);
str->mChars = NULL;
str->u.chars = NULL;
} else {
key = js_NewStringCopyN(cx, chars, length);
if (!key)

View File

@ -272,8 +272,6 @@ MarkChildren(JSTracer *trc, JSString *str)
if (str->isDependent())
MarkString(trc, str->dependentBase(), "base");
else if (str->isRope()) {
if (str->isInteriorNode())
MarkString(trc, str->interiorNodeParent(), "parent");
MarkString(trc, str->ropeLeft(), "left child");
MarkString(trc, str->ropeRight(), "right child");
}
@ -350,34 +348,108 @@ TypedMarker(JSTracer *trc, JSShortString *thing)
thing->asCell()->markIfUnmarked();
}
} /* namespace gc */
namespace detail {
static JS_ALWAYS_INLINE JSString *
Tag(JSString *str)
{
JS_ASSERT(!(size_t(str) & 1));
return (JSString *)(size_t(str) | 1);
}
static JS_ALWAYS_INLINE bool
Tagged(JSString *str)
{
return (size_t(str) & 1) != 0;
}
static JS_ALWAYS_INLINE JSString *
Untag(JSString *str)
{
JS_ASSERT((size_t(str) & 1) == 1);
return (JSString *)(size_t(str) & ~size_t(1));
}
static JS_ALWAYS_INLINE void
NonRopeTypedMarker(JSString *str)
{
JS_ASSERT(!str->isRope());
if (JSString::isStatic(str) ||
!str->asCell()->markIfUnmarked() ||
!str->isDependent()) {
return;
}
JSString *base = str->dependentBase();
if (JSString::isStatic(base))
return;
base->asCell()->markIfUnmarked();
}
} /* namespace detail */
namespace gc {
static JS_ALWAYS_INLINE void
TypedMarker(JSTracer *trc, JSString *str)
{
/*
* When marking any node of a rope, mark the entire rope. This means if
* a given rope node is already marked, we are done.
*/
JSRopeNodeIterator iter;
if (str->isRope()) {
if (str->asCell()->isMarked())
return;
str = iter.init(str);
goto not_static;
using namespace detail;
if (!str->isRope()) {
NonRopeTypedMarker(str);
return;
}
do {
for (;;) {
if (JSString::isStatic(str))
break;
not_static:
JS_ASSERT(JSTRACE_STRING == GetFinalizableTraceKind(str->asCell()->arena()->header()->thingKind));
if (!str->asCell()->markIfUnmarked())
break;
if (!str->isDependent())
break;
str = str->dependentBase();
/*
* This function must not fail, so a simple stack-based traversal must not
* be used (since it may oom if the stack grows large). Instead, strings
* are temporarily mutated to embed parent pointers as they are traversed.
* This algorithm is homomorphic to JSString::flatten.
*/
JSString *parent = NULL;
first_visit_node: {
if (!str->asCell()->markIfUnmarked())
goto finish_node;
JSString *left = str->ropeLeft();
if (left->isRope()) {
JS_ASSERT(!Tagged(str->u.left) && !Tagged(str->s.right));
str->u.left = Tag(parent);
parent = str;
str = left;
goto first_visit_node;
}
str = iter.next();
} while (str);
NonRopeTypedMarker(left);
}
visit_right_child: {
JSString *right = str->ropeRight();
if (right->isRope()) {
JS_ASSERT(!Tagged(str->u.left) && !Tagged(str->s.right));
str->s.right = Tag(parent);
parent = str;
str = right;
goto first_visit_node;
}
NonRopeTypedMarker(right);
}
finish_node: {
if (!parent)
return;
if (Tagged(parent->u.left)) {
JS_ASSERT(!Tagged(parent->s.right));
JSString *nextParent = Untag(parent->u.left);
parent->u.left = str;
str = parent;
parent = nextParent;
goto visit_right_child;
}
JS_ASSERT(Tagged(parent->s.right));
JSString *nextParent = Untag(parent->s.right);
parent->s.right = str;
str = parent;
parent = nextParent;
goto finish_node;
}
}
static inline void

View File

@ -97,90 +97,112 @@ js_GetStringChars(JSContext *cx, JSString *str)
return str->flatChars();
}
static JS_ALWAYS_INLINE size_t
RopeCapacityFor(size_t length)
{
static const size_t ROPE_DOUBLING_MAX = 1024 * 1024;
/*
* Grow by 12.5% if the buffer is very large. Otherwise, round up to the
* next power of 2. This is similar to what we do with arrays; see
* JSObject::ensureDenseArrayElements.
*/
if (length > ROPE_DOUBLING_MAX)
return length + (length / 8);
return RoundUpPow2(length);
}
void
JSString::flatten()
{
JS_ASSERT(isRope());
/*
* This can be called from any string in the rope, so first traverse to the
* top node.
*/
JSString *topNode = this;
while (topNode->isInteriorNode())
topNode = topNode->interiorNodeParent();
const size_t length = topNode->length();
const size_t capacity = topNode->topNodeCapacity();
jschar *const chars = (jschar *) topNode->topNodeBuffer();
/*
* To allow a homogeneous tree traversal, transform the top node into an
* internal node with null parent (which will be the stop condition).
*/
topNode->e.mParent = NULL;
#ifdef DEBUG
topNode->mLengthAndFlags = JSString::INTERIOR_NODE;
#endif
/*
* Perform a depth-first tree traversal, splatting each node's characters
* into a contiguous buffer. Visit each node three times:
* - the first time, record the position in the linear buffer, and recurse
* into the left child.
* - the second time, recurse into the right child.
* - the third time, transform the node into a dependent string.
* 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.
* 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 everythings works out. This
* algorithm is homomorphic to TypedMarker(JSTracer *, JSString *).
*
* 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.
*/
JSString *str = topNode;
jschar *pos = chars;
while (true) {
/* Visiting the node for the first time. */
JS_ASSERT(str->isInteriorNode());
{
JSString *next = str->mLeft;
str->mChars = pos; /* N.B. aliases mLeft */
if (next->isInteriorNode()) {
str->mLengthAndFlags = 0x200; /* N.B. flags invalidated */
str = next;
continue; /* Visit node for the first time. */
} else {
size_t len = next->length();
PodCopy(pos, next->mChars, len);
pos += len;
goto visit_right_child;
}
}
const size_t wholeLength = length();
size_t wholeCapacity;
jschar *wholeChars;
JSString *str = this;
jschar *pos;
revisit_parent:
if (str->mLengthAndFlags == 0x200) {
visit_right_child:
JSString *next = str->e.mRight;
if (next->isInteriorNode()) {
str->mLengthAndFlags = 0x300;
str = next;
continue; /* Visit 'str' for the first time. */
} else {
size_t len = next->length();
PodCopy(pos, next->mChars, len);
pos += len;
goto finish_node;
}
} else {
JS_ASSERT(str->mLengthAndFlags == 0x300);
finish_node:
JSString *next = str->e.mParent;
str->finishTraversalConversion(topNode, pos);
if (!next) {
JS_ASSERT(pos == chars + length);
*pos = 0;
topNode->initFlatExtensible(chars, length, capacity);
return;
}
str = next;
goto revisit_parent; /* Visit 'str' for the second or third time */
if (u.left->isExtensible() && u.left->s.capacity >= wholeLength) {
wholeCapacity = u.left->s.capacity;
wholeChars = u.left->u.chars;
pos = wholeChars + u.left->length();
u.left->finishTraversalConversion(this, wholeChars, pos);
goto visit_right_child;
}
wholeCapacity = RopeCapacityFor(wholeLength);
wholeChars = (jschar *)js_malloc((wholeCapacity + 1) * sizeof(jschar));
pos = wholeChars;
first_visit_node: {
JSString *left = str->u.left; /* Read before clobbered. */
str->u.chars = pos;
if (left->isRope()) {
left->s.parent = str; /* Return to this when 'left' done, */
left->lengthAndFlags = 0x200; /* but goto visit_right_child. */
str = left;
goto first_visit_node;
}
size_t len = left->length();
PodCopy(pos, left->u.chars, len);
pos += len;
}
visit_right_child: {
JSString *right = str->s.right;
if (right->isRope()) {
right->s.parent = str; /* Return to this node when 'right' done, */
right->lengthAndFlags = 0x300; /* but goto finish_node. */
str = right;
goto first_visit_node;
}
size_t len = right->length();
PodCopy(pos, right->u.chars, len);
pos += len;
}
finish_node: {
if (str == this) {
JS_ASSERT(pos == wholeChars + wholeLength);
*pos = '\0';
initFlatExtensible(wholeChars, wholeLength, wholeCapacity);
return;
}
size_t progress = str->lengthAndFlags; /* Read before clobbered. */
JSString *parent = str->s.parent;
str->finishTraversalConversion(this, wholeChars, pos);
str = parent;
if (progress == 0x200)
goto visit_right_child;
goto finish_node;
}
}
@ -201,143 +223,32 @@ JS_DEFINE_CALLINFO_1(extern, INT32, js_Flatten, STRING, 0, nanojit::ACCSET_STORE
#endif /* !JS_TRACER */
static JS_ALWAYS_INLINE size_t
RopeAllocSize(const size_t length, size_t *capacity)
{
static const size_t ROPE_DOUBLING_MAX = 1024 * 1024;
size_t size;
size_t minCap = (length + 1) * sizeof(jschar);
/*
* Grow by 12.5% if the buffer is very large. Otherwise, round up to the
* next power of 2. This is similar to what we do with arrays; see
* JSObject::ensureDenseArrayElements.
*/
if (length > ROPE_DOUBLING_MAX)
size = minCap + (minCap / 8);
else
size = RoundUpPow2(minCap);
*capacity = (size / sizeof(jschar)) - 1;
JS_ASSERT(size >= sizeof(JSRopeBufferInfo));
return size;
}
static JS_ALWAYS_INLINE JSRopeBufferInfo *
ObtainRopeBuffer(JSContext *cx, bool usingLeft, bool usingRight,
JSRopeBufferInfo *sourceBuffer, size_t length,
JSString *left, JSString *right)
{
JSRopeBufferInfo *buf;
size_t capacity;
/*
* We need to survive a GC upon failure and in case creating a new
* string header triggers a GC, but we've broken the invariant that
* rope top nodes always point to freeable JSRopeBufferInfo
* objects, so make them point to NULL.
*/
if (usingLeft)
left->nullifyTopNodeBuffer();
if (usingRight)
right->nullifyTopNodeBuffer();
/*
* Try to reuse sourceBuffer. If it's not suitable, free it and create a
* suitable buffer.
*/
if (length <= sourceBuffer->capacity) {
buf = sourceBuffer;
} else {
size_t allocSize = RopeAllocSize(length, &capacity);
cx->free(sourceBuffer);
buf = (JSRopeBufferInfo *) cx->malloc(allocSize);
if (!buf)
return NULL;
buf->capacity = capacity;
}
return buf;
}
static JS_ALWAYS_INLINE JSString *
FinishConcat(JSContext *cx, bool usingLeft, bool usingRight,
JSString *left, JSString *right, size_t length,
JSRopeBufferInfo *buf)
{
JSString *res = js_NewGCString(cx);
if (!res) {
cx->free(buf);
return NULL;
}
res->initTopNode(left, right, length, buf);
if (usingLeft)
left->convertToInteriorNode(res);
if (usingRight)
right->convertToInteriorNode(res);
return res;
}
JSString * JS_FASTCALL
js_ConcatStrings(JSContext *cx, JSString *left, JSString *right)
{
size_t length, leftLen, rightLen;
bool leftRopeTop, rightRopeTop;
leftLen = left->length();
size_t leftLen = left->length();
if (leftLen == 0)
return right;
rightLen = right->length();
size_t rightLen = right->length();
if (rightLen == 0)
return left;
length = leftLen + rightLen;
size_t wholeLength = leftLen + rightLen;
if (JSShortString::fitsIntoShortString(length)) {
if (JSShortString::fitsIntoShortString(wholeLength)) {
JSShortString *shortStr = js_NewGCShortString(cx);
if (!shortStr)
return NULL;
jschar *buf = shortStr->init(length);
jschar *buf = shortStr->init(wholeLength);
js_short_strncpy(buf, left->chars(), leftLen);
js_short_strncpy(buf + leftLen, right->chars(), rightLen);
buf[length] = 0;
buf[wholeLength] = 0;
return shortStr->header();
}
/*
* We need to enforce a tree structure in ropes: every node needs to have a
* unique parent. So, we can't have the left or right child be in the middle
* of a rope tree. One potential solution is to traverse the subtree for the
* argument string and create a new flat string, but that would add
* complexity and is a rare case, so we simply flatten the entire rope that
* contains it. The case where left and right are part of the same rope is
* handled implicitly.
*/
if (left->isInteriorNode())
left->flatten();
if (right->isInteriorNode())
right->flatten();
if (left->isExtensible() && !right->isRope() &&
left->flatCapacity() >= length) {
JS_ASSERT(left->isFlat());
/*
* If left has enough unused space at the end of its buffer that we can
* fit the entire new string there, just write there.
*/
jschar *chars = left->chars();
js_strncpy(chars + leftLen, right->chars(), rightLen);
chars[length] = 0;
JSString *res = js_NewString(cx, chars, length);
if (!res)
return NULL;
res->initFlatExtensible(chars, length, left->flatCapacity());
left->initDependent(res, res->flatChars(), leftLen);
return res;
}
if (length > JSString::MAX_LENGTH) {
if (wholeLength > JSString::MAX_LENGTH) {
if (JS_ON_TRACE(cx)) {
if (!CanLeaveTrace(cx))
return NULL;
@ -347,67 +258,12 @@ js_ConcatStrings(JSContext *cx, JSString *left, JSString *right)
return NULL;
}
leftRopeTop = left->isTopNode();
rightRopeTop = right->isTopNode();
JSString *newRoot = js_NewGCString(cx);
if (!newRoot)
return NULL;
/*
* To make traversal more manageable, we enforce that, unless the children
* are leaves, the two children of a rope node must be distinct.
*/
if (left == right && leftRopeTop) {
left->flatten();
leftRopeTop = false;
rightRopeTop = false;
JS_ASSERT(leftLen == left->length());
JS_ASSERT(rightLen == right->length());
JS_ASSERT(!left->isTopNode());
JS_ASSERT(!right->isTopNode());
}
/*
* There are 4 cases, based on whether on whether the left or right is a
* rope or non-rope string.
*/
JSRopeBufferInfo *buf = NULL;
if (leftRopeTop) {
/* Left child is a rope. */
JSRopeBufferInfo *leftBuf = left->topNodeBuffer();
/* If both children are ropes, steal the larger buffer. */
if (JS_UNLIKELY(rightRopeTop)) {
JSRopeBufferInfo *rightBuf = right->topNodeBuffer();
/* Put the larger buffer into 'leftBuf'. */
if (leftBuf->capacity >= rightBuf->capacity) {
cx->free(rightBuf);
} else {
cx->free(leftBuf);
leftBuf = rightBuf;
}
}
buf = ObtainRopeBuffer(cx, true, rightRopeTop, leftBuf, length, left, right);
if (!buf)
return NULL;
} else if (JS_UNLIKELY(rightRopeTop)) {
/* Right child is a rope: steal its buffer if big enough. */
JSRopeBufferInfo *rightBuf = right->topNodeBuffer();
buf = ObtainRopeBuffer(cx, false, true, rightBuf, length, left, right);
if (!buf)
return NULL;
} else {
/* Neither child is a rope: need to make a new buffer. */
size_t capacity;
size_t allocSize = RopeAllocSize(length, &capacity);
buf = (JSRopeBufferInfo *) cx->malloc(allocSize);
if (!buf)
return NULL;
buf->capacity = capacity;
}
return FinishConcat(cx, leftRopeTop, rightRopeTop, left, right, length, buf);
newRoot->initRopeNode(left, right, wholeLength);
return newRoot;
}
const jschar *
@ -1359,15 +1215,24 @@ StringMatch(const jschar *text, jsuint textlen,
static const size_t sRopeMatchThresholdRatioLog2 = 5;
static jsint
RopeMatch(JSString *textstr, const jschar *pat, jsuint patlen)
/*
* RopeMatch takes the text to search, the patern to search for in the text.
* RopeMatch returns false on OOM and otherwise returns the match index through
* the 'match' outparam (-1 for not found).
*/
static bool
RopeMatch(JSContext *cx, JSString *textstr, const jschar *pat, jsuint patlen, jsint *match)
{
JS_ASSERT(textstr->isTopNode());
JS_ASSERT(textstr->isRope());
if (patlen == 0)
return 0;
if (textstr->length() < patlen)
return -1;
if (patlen == 0) {
*match = 0;
return true;
}
if (textstr->length() < patlen) {
*match = -1;
return true;
}
/*
* List of leaf nodes in the rope. If we run out of memory when trying to
@ -1382,12 +1247,20 @@ RopeMatch(JSString *textstr, const jschar *pat, jsuint patlen)
* need to build the list of leaf nodes. Do both here: iterate over the
* nodes so long as there are not too many.
*/
size_t textstrlen = textstr->length();
size_t threshold = textstrlen >> sRopeMatchThresholdRatioLog2;
JSRopeLeafIterator iter;
for (JSString *str = iter.init(textstr); str; str = iter.next()) {
if (threshold-- == 0 || !strs.append(str))
return StringMatch(textstr->chars(), textstrlen, pat, patlen);
{
size_t textstrlen = textstr->length();
size_t threshold = textstrlen >> sRopeMatchThresholdRatioLog2;
StringSegmentRange r(cx);
if (!r.init(textstr))
return false;
while (!r.empty()) {
if (threshold-- == 0 || !strs.append(r.front())) {
*match = StringMatch(textstr->chars(), textstrlen, pat, patlen);
return true;
}
if (!r.popFront())
return false;
}
}
/* Absolute offset from the beginning of the logical string textstr. */
@ -1401,8 +1274,10 @@ RopeMatch(JSString *textstr, const jschar *pat, jsuint patlen)
size_t len;
(*outerp)->getCharsAndLength(chars, len);
jsint matchResult = StringMatch(chars, len, pat, patlen);
if (matchResult != -1)
return pos + matchResult;
if (matchResult != -1) {
*match = pos + matchResult;
return true;
}
/* Test the overlap. */
JSString **innerp = outerp;
@ -1422,8 +1297,10 @@ RopeMatch(JSString *textstr, const jschar *pat, jsuint patlen)
const jschar *ttend = textend;
for (const jschar *pp = p1, *tt = t; pp != patend; ++pp, ++tt) {
while (tt == ttend) {
if (++innerp == strs.end())
return -1;
if (++innerp == strs.end()) {
*match = -1;
return true;
}
(*innerp)->getCharsAndEnd(tt, ttend);
}
if (*pp != *tt)
@ -1431,7 +1308,8 @@ RopeMatch(JSString *textstr, const jschar *pat, jsuint patlen)
}
/* Matched! */
return pos + (t - chars) - 1; /* -1 because of *t++ above */
*match = pos + (t - chars) - 1; /* -1 because of *t++ above */
return true;
break_continue:;
}
@ -1439,7 +1317,8 @@ RopeMatch(JSString *textstr, const jschar *pat, jsuint patlen)
pos += len;
}
return -1;
*match = -1;
return true;
}
static JSBool
@ -1734,9 +1613,12 @@ class RegExpGuard
*
* @param checkMetaChars Look for regexp metachars in the pattern string.
* @return Whether flat matching could be used.
*
* N.B. tryFlatMatch returns NULL on OOM, so the caller must check cx->throwing.
*/
const FlatMatch *
tryFlatMatch(JSString *textstr, uintN optarg, uintN argc, bool checkMetaChars = true)
tryFlatMatch(JSContext *cx, JSString *textstr, uintN optarg, uintN argc,
bool checkMetaChars = true)
{
if (rep.re_)
return NULL;
@ -1755,8 +1637,9 @@ class RegExpGuard
* textstr could be a rope, so we want to avoid flattening it for as
* long as possible.
*/
if (textstr->isTopNode()) {
fm.match_ = RopeMatch(textstr, fm.pat, fm.patlen);
if (textstr->isRope()) {
if (!RopeMatch(cx, textstr, fm.pat, fm.patlen, &fm.match_))
return NULL;
} else {
const jschar *text;
size_t textlen;
@ -1918,8 +1801,10 @@ str_match(JSContext *cx, uintN argc, Value *vp)
RegExpGuard g(cx);
if (!g.init(argc, vp))
return false;
if (const FlatMatch *fm = g.tryFlatMatch(str, 1, argc))
if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, argc))
return BuildFlatMatchArray(cx, str, *fm, vp);
if (cx->throwing) /* from tryFlatMatch */
return false;
const RegExpPair *rep = g.normalizeRegExp(false, 1, argc, vp);
if (!rep)
@ -1946,10 +1831,12 @@ str_search(JSContext *cx, uintN argc, Value *vp)
RegExpGuard g(cx);
if (!g.init(argc, vp))
return false;
if (const FlatMatch *fm = g.tryFlatMatch(str, 1, argc)) {
if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, argc)) {
vp->setInt32(fm->match());
return true;
}
if (cx->throwing) /* from tryFlatMatch */
return false;
const RegExpPair *rep = g.normalizeRegExp(false, 1, argc, vp);
if (!rep)
return false;
@ -2273,18 +2160,21 @@ static bool
BuildFlatReplacement(JSContext *cx, JSString *textstr, JSString *repstr,
const FlatMatch &fm, Value *vp)
{
JSRopeBuilder builder(cx);
size_t match = fm.match(); /* Avoid signed/unsigned warnings. */
RopeBuilder builder(cx);
size_t match = fm.match();
size_t matchEnd = match + fm.patternLength();
if (textstr->isTopNode()) {
if (textstr->isRope()) {
/*
* If we are replacing over a rope, avoid flattening it by iterating
* through it, building a new rope.
*/
JSRopeLeafIterator iter;
StringSegmentRange r(cx);
if (!r.init(textstr))
return false;
size_t pos = 0;
for (JSString *str = iter.init(textstr); str; str = iter.next()) {
while (!r.empty()) {
JSString *str = r.front();
size_t len = str->length();
size_t strEnd = pos + len;
if (pos < matchEnd && strEnd > match) {
@ -2322,6 +2212,8 @@ BuildFlatReplacement(JSContext *cx, JSString *textstr, JSString *repstr,
return false;
}
pos += str->length();
if (!r.popFront())
return false;
}
} else {
JSString *leftSide = js_NewDependentString(cx, textstr, 0, match);
@ -2337,7 +2229,7 @@ BuildFlatReplacement(JSContext *cx, JSString *textstr, JSString *repstr,
}
}
vp->setString(builder.getStr());
vp->setString(builder.result());
return true;
}
@ -2411,13 +2303,13 @@ BuildDollarReplacement(JSContext *cx, JSString *textstr, JSString *repstr,
textstr->length() - matchLimit);
ENSURE(rightSide);
JSRopeBuilder builder(cx);
RopeBuilder builder(cx);
ENSURE(builder.append(leftSide) &&
builder.append(newReplace) &&
builder.append(rightSide));
#undef ENSURE
vp->setString(builder.getStr());
vp->setString(builder.result());
return true;
}
@ -2497,14 +2389,14 @@ str_replace_flat_lambda(JSContext *cx, uintN argc, Value *vp, ReplaceData &rdata
if (!rightSide)
return false;
JSRopeBuilder builder(cx);
RopeBuilder builder(cx);
if (!(builder.append(leftSide) &&
builder.append(repstr) &&
builder.append(rightSide))) {
return false;
}
vp->setString(builder.getStr());
vp->setString(builder.result());
return true;
}
@ -2583,8 +2475,10 @@ js::str_replace(JSContext *cx, uintN argc, Value *vp)
* |RegExp| statics.
*/
const FlatMatch *fm = rdata.g.tryFlatMatch(rdata.str, optarg, argc, false);
const FlatMatch *fm = rdata.g.tryFlatMatch(cx, rdata.str, optarg, argc, false);
if (!fm) {
if (cx->throwing) /* from tryFlatMatch */
return false;
JS_ASSERT_IF(!rdata.g.hasRegExpPair(), argc > optarg);
return str_replace_regexp(cx, argc, vp, rdata);
}
@ -3204,14 +3098,17 @@ static JSFunctionSpec string_methods[] = {
#define R3(n) R2(n), R2((n) + (1 << 2))
#define R7(n) R6(n), R6((n) + (1 << 6))
#define BUILD_LENGTH_AND_FLAGS(length, flags) \
(((length) << JSString::LENGTH_SHIFT) | (flags))
/*
* Declare unit strings. Pack the string data itself into the mInlineChars
* place in the header.
*/
#define R(c) { \
JSString::FLAT | JSString::ATOMIZED | (1 << JSString::FLAGS_LENGTH_SHIFT),\
BUILD_LENGTH_AND_FLAGS(1, JSString::FLAT | JSString::ATOMIZED), \
{ (jschar *)(((char *)(unitStringTable + (c))) + \
offsetof(JSString, mInlineStorage)) }, \
offsetof(JSString, inlineStorage)) }, \
{ {(c), 0x00} } }
#ifdef __SUNPRO_CC
@ -3269,9 +3166,9 @@ const jschar JSString::fromSmallChar[] = { R6(0) };
* second character.
*/
#define R(c) { \
JSString::FLAT | JSString::ATOMIZED | (2 << JSString::FLAGS_LENGTH_SHIFT),\
BUILD_LENGTH_AND_FLAGS(2, JSString::FLAT | JSString::ATOMIZED), \
{ (jschar *)(((char *)(length2StringTable + (c))) + \
offsetof(JSString, mInlineStorage)) }, \
offsetof(JSString, inlineStorage)) }, \
{ {FROM_SMALL_CHAR((c) >> 6), FROM_SMALL_CHAR((c) & 0x3F), 0x00} } }
#ifdef __SUNPRO_CC
@ -3302,9 +3199,9 @@ __attribute__ ((aligned (8)))
* correct location of the int string.
*/
#define R(c) { \
JSString::FLAT | JSString::ATOMIZED | (3 << JSString::FLAGS_LENGTH_SHIFT),\
BUILD_LENGTH_AND_FLAGS(3, JSString::FLAT | JSString::ATOMIZED), \
{ (jschar *)(((char *)(hundredStringTable + ((c) - 100))) + \
offsetof(JSString, mInlineStorage)) }, \
offsetof(JSString, inlineStorage)) }, \
{ {((c) / 100) + '0', ((c) / 10 % 10) + '0', ((c) % 10) + '0', 0x00} } }

View File

@ -57,9 +57,6 @@
#include "jsvalue.h"
#include "jscell.h"
#define JSSTRING_BIT(n) ((size_t)1 << (n))
#define JSSTRING_BITMASK(n) (JSSTRING_BIT(n) - 1)
enum {
UNIT_STRING_LIMIT = 256U,
SMALL_CHAR_LIMIT = 128U, /* Bigger chars cannot be in a length-2 string. */
@ -108,98 +105,80 @@ namespace js { namespace mjit {
* threads.
*
* When the string is DEPENDENT, the string depends on characters of another
* string strongly referenced by the mBase field. The base member may point to
* string strongly referenced by the base field. The base member may point to
* another dependent string if chars() has not been called yet.
*
* To optimize js_ConcatStrings and some other cases, we lazily concatenate
* strings when possible, creating concatenation trees, a.k.a. ropes. A string
* is an INTERIOR_NODE if it is a non-root, non-leaf node in a rope, and a
* string is a TOP_NODE if it is the root of a rope. In order to meet API
* requirements, chars() is not allowed to fail, so we build ropes so that they
* form a well-defined tree structure, and the top node of every rope contains
* an (almost) empty buffer that is large enough to contain the entire string.
* Whenever chars() is called on a rope, it traverses its tree and fills that
* buffer in, and when concatenating strings, we reuse these empty buffers
* whenever possible, so that we can build a string through concatenation in
* linear time, and have relatively few malloc calls when doing so.
*
* NB: Always use the length() and chars() accessor methods.
* When a string is a ROPE, it represents the lazy concatenation of other
* strings. In general, the nodes reachable from any rope form a dag.
*/
struct JSString {
struct JSString
{
friend class js::TraceRecorder;
friend class js::mjit::Compiler;
friend JSAtom *
js_AtomizeString(JSContext *cx, JSString *str, uintN flags);
public:
friend JSAtom *js_AtomizeString(JSContext *cx, JSString *str, uintN flags);
/*
* Not private because we want to be able to use static
* initializers for them. Don't use these directly!
* Not private because we want to be able to use static initializers for
* them. Don't use these directly! FIXME bug 614459.
*/
size_t mLengthAndFlags; /* in all strings */
size_t lengthAndFlags; /* in all strings */
union {
jschar *mChars; /* in flat and dependent strings */
JSString *mLeft; /* in rope interior and top nodes */
};
jschar *chars; /* in non-rope strings */
JSString *left; /* in rope strings */
} u;
union {
/*
* We may keep more than 4 inline chars, but 4 is necessary for all of
* our static initialization.
*/
jschar mInlineStorage[4]; /* In short strings. */
jschar inlineStorage[4]; /* in short strings */
struct {
union {
size_t mCapacity; /* in extensible flat strings (optional) */
JSString *mParent; /* in rope interior nodes */
JSRopeBufferInfo *mBufferWithInfo; /* in rope top nodes */
JSString *right; /* in rope strings */
JSString *base; /* in dependent strings */
size_t capacity; /* in extensible flat strings */
};
union {
JSString *mBase; /* in dependent strings */
JSString *mRight; /* in rope interior and top nodes */
JSString *parent; /* temporarily used during flatten */
size_t reserved; /* may use for bug 615290 */
};
} e;
uintN externalStringType; /* for external strings. */
} s;
size_t externalStringType; /* in external strings */
};
/*
* The mLengthAndFlags field in string headers has data arranged in the
* The lengthAndFlags field in string headers has data arranged in the
* following way:
*
* [ length (bits 4-31) ][ flags (bits 2-3) ][ type (bits 0-1) ]
*
* The length is packed in mLengthAndFlags, even in string types that don't
* The length is packed in lengthAndFlags, even in string types that don't
* need 3 other fields, to make the length check simpler.
*
* When the string type is FLAT, the flags can contain ATOMIZED or
* EXTENSIBLE.
*
* When the string type is INTERIOR_NODE or TOP_NODE, the flags area is
* used to store the rope traversal count.
*/
static const size_t FLAT = 0;
static const size_t DEPENDENT = 1;
static const size_t INTERIOR_NODE = 2;
static const size_t TOP_NODE = 3;
static const size_t TYPE_FLAGS_MASK = JS_BITMASK(4);
static const size_t LENGTH_SHIFT = 4;
/* Rope/non-rope can be checked by checking one bit. */
static const size_t ROPE_BIT = JSSTRING_BIT(1);
static const size_t TYPE_MASK = JS_BITMASK(2);
static const size_t FLAT = 0x0;
static const size_t DEPENDENT = 0x1;
static const size_t ROPE = 0x2;
static const size_t ATOMIZED = JSSTRING_BIT(2);
static const size_t EXTENSIBLE = JSSTRING_BIT(3);
/* Allow checking 1 bit for dependent/rope strings. */
static const size_t DEPENDENT_BIT = JS_BIT(0);
static const size_t ROPE_BIT = JS_BIT(1);
static const size_t FLAGS_LENGTH_SHIFT = 4;
static const size_t ATOMIZED = JS_BIT(2);
static const size_t EXTENSIBLE = JS_BIT(3);
static const size_t TYPE_MASK = JSSTRING_BITMASK(2);
static const size_t TYPE_FLAGS_MASK = JSSTRING_BITMASK(4);
inline bool hasFlag(size_t flag) const {
return (mLengthAndFlags & flag) != 0;
size_t buildLengthAndFlags(size_t length, size_t flags) {
return (length << LENGTH_SHIFT) | flags;
}
inline js::gc::Cell *asCell() {
return reinterpret_cast<js::gc::Cell *>(this);
}
inline js::gc::FreeCell *asFreeCell() {
return reinterpret_cast<js::gc::FreeCell *>(this);
}
@ -210,50 +189,39 @@ struct JSString {
*/
static const size_t MAX_LENGTH = (1 << 28) - 1;
inline size_t type() const {
return mLengthAndFlags & TYPE_MASK;
}
JS_ALWAYS_INLINE bool isDependent() const {
return type() == DEPENDENT;
return lengthAndFlags & DEPENDENT_BIT;
}
JS_ALWAYS_INLINE bool isFlat() const {
return type() == FLAT;
return (lengthAndFlags & TYPE_MASK) == FLAT;
}
inline bool isExtensible() const {
return isFlat() && hasFlag(EXTENSIBLE);
}
inline bool isRope() const {
return hasFlag(ROPE_BIT);
JS_ALWAYS_INLINE bool isExtensible() const {
JS_ASSERT_IF(lengthAndFlags & EXTENSIBLE, isFlat());
return lengthAndFlags & EXTENSIBLE;
}
JS_ALWAYS_INLINE bool isAtomized() const {
return isFlat() && hasFlag(ATOMIZED);
JS_ASSERT_IF(lengthAndFlags & ATOMIZED, isFlat());
return lengthAndFlags & ATOMIZED;
}
inline bool isInteriorNode() const {
return type() == INTERIOR_NODE;
}
inline bool isTopNode() const {
return type() == TOP_NODE;
JS_ALWAYS_INLINE bool isRope() const {
return lengthAndFlags & ROPE_BIT;
}
JS_ALWAYS_INLINE jschar *chars() {
if (JS_UNLIKELY(isRope()))
flatten();
return mChars;
ensureNotRope();
return u.chars;
}
JS_ALWAYS_INLINE size_t length() const {
return mLengthAndFlags >> FLAGS_LENGTH_SHIFT;
return lengthAndFlags >> LENGTH_SHIFT;
}
JS_ALWAYS_INLINE bool empty() const {
return length() == 0;
return lengthAndFlags <= TYPE_FLAGS_MASK;
}
JS_ALWAYS_INLINE void getCharsAndLength(const jschar *&chars, size_t &length) {
@ -265,18 +233,11 @@ struct JSString {
end = length() + (chars = this->chars());
}
JS_ALWAYS_INLINE jschar *inlineStorage() {
JS_ASSERT(isFlat());
return mInlineStorage;
}
JS_ALWAYS_INLINE void initFlatNotTerminated(jschar *chars, size_t length) {
JS_ASSERT(length <= MAX_LENGTH);
JS_ASSERT(!isStatic(this));
e.mBase = NULL;
e.mCapacity = 0;
mLengthAndFlags = (length << FLAGS_LENGTH_SHIFT) | FLAT;
mChars = chars;
lengthAndFlags = buildLengthAndFlags(length, FLAT);
u.chars = chars;
}
/* Specific flat string initializer and accessor methods. */
@ -287,24 +248,24 @@ struct JSString {
JS_ALWAYS_INLINE void initShortString(jschar *chars, size_t length) {
JS_ASSERT(length <= MAX_LENGTH);
JS_ASSERT(chars >= inlineStorage && chars < (jschar *)(this + 2));
JS_ASSERT(!isStatic(this));
mLengthAndFlags = (length << FLAGS_LENGTH_SHIFT) | FLAT;
mChars = chars;
lengthAndFlags = buildLengthAndFlags(length, FLAT);
u.chars = chars;
}
JS_ALWAYS_INLINE void initFlatExtensible(jschar *chars, size_t length, size_t cap) {
JS_ASSERT(length <= MAX_LENGTH);
JS_ASSERT(chars[length] == jschar(0));
JS_ASSERT(!isStatic(this));
e.mBase = NULL;
e.mCapacity = cap;
mLengthAndFlags = (length << FLAGS_LENGTH_SHIFT) | FLAT | EXTENSIBLE;
mChars = chars;
lengthAndFlags = buildLengthAndFlags(length, FLAT | EXTENSIBLE);
u.chars = chars;
s.capacity = cap;
}
JS_ALWAYS_INLINE jschar *flatChars() const {
JS_ASSERT(isFlat());
return mChars;
return u.chars;
}
JS_ALWAYS_INLINE size_t flatLength() const {
@ -312,48 +273,44 @@ struct JSString {
return length();
}
JS_ALWAYS_INLINE size_t flatCapacity() const {
JS_ASSERT(isFlat());
return e.mCapacity;
}
inline void flatSetAtomized() {
JS_ASSERT(isFlat());
JS_ASSERT(!isStatic(this));
mLengthAndFlags |= ATOMIZED;
lengthAndFlags |= ATOMIZED;
}
inline void flatClearExtensible() {
JS_ASSERT(isFlat());
/*
* We cannot eliminate the flag check before writing to mLengthAndFlags as
* static strings may reside in write-protected memory. See bug 599481.
* N.B. This may be called on static strings, which may be in read-only
* memory, so we cannot unconditionally apply the mask.
*/
if (mLengthAndFlags & EXTENSIBLE)
mLengthAndFlags &= ~EXTENSIBLE;
JS_ASSERT(isFlat());
if (lengthAndFlags & EXTENSIBLE)
lengthAndFlags &= ~EXTENSIBLE;
}
/*
* The chars pointer should point somewhere inside the buffer owned by bstr.
* The caller still needs to pass bstr for GC purposes.
* The chars pointer should point somewhere inside the buffer owned by base.
* The caller still needs to pass base for GC purposes.
*/
inline void initDependent(JSString *bstr, jschar *chars, size_t len) {
JS_ASSERT(len <= MAX_LENGTH);
inline void initDependent(JSString *base, jschar *chars, size_t length) {
JS_ASSERT(!isStatic(this));
e.mParent = NULL;
mChars = chars;
mLengthAndFlags = DEPENDENT | (len << FLAGS_LENGTH_SHIFT);
e.mBase = bstr;
JS_ASSERT(base->isFlat());
JS_ASSERT(chars >= base->chars() && chars < base->chars() + base->length());
JS_ASSERT(length <= base->length() - (chars - base->chars()));
lengthAndFlags = buildLengthAndFlags(length, DEPENDENT);
u.chars = chars;
s.base = base;
}
inline JSString *dependentBase() const {
JS_ASSERT(isDependent());
return e.mBase;
return s.base;
}
JS_ALWAYS_INLINE jschar *dependentChars() {
return mChars;
JS_ASSERT(isDependent());
return u.chars;
}
inline size_t dependentLength() const {
@ -361,73 +318,48 @@ struct JSString {
return length();
}
/* Rope-related initializers and accessors. */
inline void initTopNode(JSString *left, JSString *right, size_t len,
JSRopeBufferInfo *buf) {
JS_ASSERT(left->length() + right->length() <= MAX_LENGTH);
JS_ASSERT(!isStatic(this));
mLengthAndFlags = TOP_NODE | (len << FLAGS_LENGTH_SHIFT);
mLeft = left;
e.mRight = right;
e.mBufferWithInfo = buf;
}
inline void convertToInteriorNode(JSString *parent) {
JS_ASSERT(isTopNode());
e.mParent = parent;
mLengthAndFlags = INTERIOR_NODE | (length() << FLAGS_LENGTH_SHIFT);
}
inline JSString *interiorNodeParent() const {
JS_ASSERT(isInteriorNode());
return e.mParent;
}
inline JSString *ropeLeft() const {
JS_ASSERT(isRope());
return mLeft;
}
inline JSString *ropeRight() const {
JS_ASSERT(isRope());
return e.mRight;
}
inline size_t topNodeCapacity() const {
JS_ASSERT(isTopNode());
return e.mBufferWithInfo->capacity;
}
inline JSRopeBufferInfo *topNodeBuffer() const {
JS_ASSERT(isTopNode());
return e.mBufferWithInfo;
}
inline void nullifyTopNodeBuffer() {
JS_ASSERT(isTopNode());
e.mBufferWithInfo = NULL;
}
inline void finishTraversalConversion(JSString *base, jschar *end) {
mLengthAndFlags = JSString::DEPENDENT |
((end - mChars) << JSString::FLAGS_LENGTH_SHIFT);
e.mBase = base;
}
const jschar *undepend(JSContext *cx);
inline bool ensureNotDependent(JSContext *cx) {
return !isDependent() || undepend(cx);
}
inline void ensureNotRope() {
const jschar *nonRopeChars() const {
JS_ASSERT(!isRope());
return u.chars;
}
/* Rope-related initializers and accessors. */
inline void initRopeNode(JSString *left, JSString *right, size_t length) {
JS_ASSERT(left->length() + right->length() == length);
lengthAndFlags = buildLengthAndFlags(length, ROPE);
u.left = left;
s.right = right;
}
inline JSString *ropeLeft() const {
JS_ASSERT(isRope());
return u.left;
}
inline JSString *ropeRight() const {
JS_ASSERT(isRope());
return s.right;
}
inline void finishTraversalConversion(JSString *base, jschar *baseBegin, jschar *end) {
JS_ASSERT(baseBegin <= u.chars && u.chars <= end);
lengthAndFlags = buildLengthAndFlags(end - u.chars, DEPENDENT);
s.base = base;
}
void flatten();
void ensureNotRope() {
if (isRope())
flatten();
}
const jschar *undepend(JSContext *cx);
/* By design, this is not allowed to fail. */
void flatten();
typedef uint8 SmallChar;
static inline bool fitsInSmallChar(jschar c) {
@ -495,11 +427,25 @@ struct JSString {
static JSString *intString(jsint i);
static JSString *lookupStaticString(const jschar *chars, size_t length);
JS_ALWAYS_INLINE void finalize(JSContext *cx);
static size_t offsetOfLengthAndFlags() {
return offsetof(JSString, lengthAndFlags);
}
static size_t offsetOfChars() {
return offsetof(JSString, u.chars);
}
static void staticAsserts() {
JS_STATIC_ASSERT(((JSString::MAX_LENGTH << JSString::LENGTH_SHIFT) >>
JSString::LENGTH_SHIFT) == JSString::MAX_LENGTH);
}
};
struct JSExternalString : JSString {
struct JSExternalString : JSString
{
static const uintN TYPE_LIMIT = 8;
static JSStringFinalizeOp str_finalizers[TYPE_LIMIT];
@ -525,10 +471,12 @@ JS_STATIC_ASSERT(sizeof(JSString) == sizeof(JSExternalString));
* mallocing the string buffer for a small string. We keep 2 string headers'
* worth of space in short strings so that more strings can be stored this way.
*/
struct JSShortString : js::gc::Cell {
class JSShortString : public js::gc::Cell
{
JSString mHeader;
JSString mDummy;
public:
/*
* Set the length of the string, and return a buffer for the caller to write
* to. This buffer must be written immediately, and should not be modified
@ -536,16 +484,16 @@ struct JSShortString : js::gc::Cell {
*/
inline jschar *init(size_t length) {
JS_ASSERT(length <= MAX_SHORT_STRING_LENGTH);
mHeader.initShortString(mHeader.inlineStorage(), length);
return mHeader.inlineStorage();
mHeader.initShortString(mHeader.inlineStorage, length);
return mHeader.inlineStorage;
}
inline jschar *getInlineStorageBeforeInit() {
return mHeader.mInlineStorage;
return mHeader.inlineStorage;
}
inline void initAtOffsetInBuffer(jschar *p, size_t length) {
JS_ASSERT(p >= mHeader.mInlineStorage && p < mHeader.mInlineStorage + MAX_SHORT_STRING_LENGTH);
JS_ASSERT(p >= mHeader.inlineStorage && p < mHeader.inlineStorage + MAX_SHORT_STRING_LENGTH);
mHeader.initShortString(p, length);
}
@ -557,147 +505,44 @@ struct JSShortString : js::gc::Cell {
return &mHeader;
}
static const size_t FREE_STRING_WORDS = 2;
static const size_t MAX_SHORT_STRING_LENGTH =
((sizeof(JSString) + 2 * sizeof(size_t)) / sizeof(jschar)) - 1;
((sizeof(JSString) + FREE_STRING_WORDS * sizeof(size_t)) / sizeof(jschar)) - 1;
static inline bool fitsIntoShortString(size_t length) {
return length <= MAX_SHORT_STRING_LENGTH;
}
JS_ALWAYS_INLINE void finalize(JSContext *cx);
static void staticAsserts() {
JS_STATIC_ASSERT(offsetof(JSString, inlineStorage) ==
sizeof(JSString) - JSShortString::FREE_STRING_WORDS * sizeof(void *));
JS_STATIC_ASSERT(offsetof(JSShortString, mDummy) == sizeof(JSString));
JS_STATIC_ASSERT(offsetof(JSString, inlineStorage) +
sizeof(jschar) * (JSShortString::MAX_SHORT_STRING_LENGTH + 1) ==
sizeof(JSShortString));
}
};
/*
* We're doing some tricks to give us more space for short strings, so make
* sure that space is ordered in the way we expect.
*/
JS_STATIC_ASSERT(offsetof(JSString, mInlineStorage) == 2 * sizeof(void *));
JS_STATIC_ASSERT(offsetof(JSShortString, mDummy) == sizeof(JSString));
JS_STATIC_ASSERT(offsetof(JSString, mInlineStorage) +
sizeof(jschar) * (JSShortString::MAX_SHORT_STRING_LENGTH + 1) ==
sizeof(JSShortString));
namespace js {
/*
* An iterator that iterates through all nodes in a rope (the top node, the
* interior nodes, and the leaves) without writing to any of the nodes.
* When an algorithm does not need a string represented as a single linear
* array of characters, this range utility may be used to traverse the string a
* sequence of linear arrays of characters. This avoids flattening ropes.
*
* It is safe to iterate through a rope in this way, even when something else is
* already iterating through it.
*
* To use, pass any node of the rope into the constructor. The first call should
* be to init, which returns the first node, and each subsequent call should
* be to next. NULL is returned when there are no more nodes to return.
* Implemented in jsstrinlines.h.
*/
class JSRopeNodeIterator {
private:
JSString *mStr;
size_t mUsedFlags;
static const size_t DONE_LEFT = 0x1;
static const size_t DONE_RIGHT = 0x2;
public:
JSRopeNodeIterator()
: mStr(NULL), mUsedFlags(0)
{}
JSString *init(JSString *str) {
/* Move to the farthest-left leaf in the rope. */
mStr = str;
while (mStr->isInteriorNode())
mStr = mStr->interiorNodeParent();
while (mStr->ropeLeft()->isInteriorNode())
mStr = mStr->ropeLeft();
JS_ASSERT(mUsedFlags == 0);
return mStr;
}
JSString *next() {
if (!mStr)
return NULL;
if (!mStr->ropeLeft()->isInteriorNode() && !(mUsedFlags & DONE_LEFT)) {
mUsedFlags |= DONE_LEFT;
return mStr->ropeLeft();
}
if (!mStr->ropeRight()->isInteriorNode() && !(mUsedFlags & DONE_RIGHT)) {
mUsedFlags |= DONE_RIGHT;
return mStr->ropeRight();
}
if (mStr->ropeRight()->isInteriorNode()) {
/*
* If we have a right child, go right once, then left as far as
* possible.
*/
mStr = mStr->ropeRight();
while (mStr->ropeLeft()->isInteriorNode())
mStr = mStr->ropeLeft();
} else {
/*
* If we have no right child, follow our parent until we move
* up-right.
*/
JSString *prev;
do {
prev = mStr;
/* Set the string to NULL if we reach the end of the tree. */
mStr = mStr->isInteriorNode() ? mStr->interiorNodeParent() : NULL;
} while (mStr && mStr->ropeRight() == prev);
}
mUsedFlags = 0;
return mStr;
}
};
class StringSegmentRange;
/*
* An iterator that returns the leaves of a rope (which hold the actual string
* data) in order. The usage is the same as JSRopeNodeIterator.
* Utility for building a rope (lazy concatenation) of strings.
*/
class JSRopeLeafIterator {
private:
JSRopeNodeIterator mNodeIterator;
class RopeBuilder;
public:
inline JSString *init(JSString *str) {
JS_ASSERT(str->isTopNode());
str = mNodeIterator.init(str);
while (str->isRope()) {
str = mNodeIterator.next();
JS_ASSERT(str);
}
return str;
}
inline JSString *next() {
JSString *str;
do {
str = mNodeIterator.next();
} while (str && str->isRope());
return str;
}
};
class JSRopeBuilder {
JSContext * const cx;
JSString *mStr;
public:
JSRopeBuilder(JSContext *cx);
inline bool append(JSString *str) {
mStr = js_ConcatStrings(cx, mStr, str);
return !!mStr;
}
inline JSString *getStr() {
return mStr;
}
};
JS_STATIC_ASSERT(JSString::INTERIOR_NODE & JSString::ROPE_BIT);
JS_STATIC_ASSERT(JSString::TOP_NODE & JSString::ROPE_BIT);
JS_STATIC_ASSERT(((JSString::MAX_LENGTH << JSString::FLAGS_LENGTH_SHIFT) >>
JSString::FLAGS_LENGTH_SHIFT) == JSString::MAX_LENGTH);
} /* namespace js */
extern const jschar *
js_GetStringChars(JSContext *cx, JSString *str);

View File

@ -133,16 +133,14 @@ JSString::finalize(JSContext *cx) {
* for a null pointer on its own.
*/
cx->free(flatChars());
} else if (isTopNode()) {
cx->free(topNodeBuffer());
}
}
inline void
JSShortString::finalize(JSContext *cx)
{
JS_ASSERT(!JSString::isStatic(header()));
JS_ASSERT(header()->isFlat());
JS_ASSERT(!JSString::isStatic(&mHeader));
JS_ASSERT(mHeader.isFlat());
JS_RUNTIME_UNMETER(cx->runtime, liveStrings);
}
@ -177,8 +175,75 @@ JSExternalString::finalize()
}
}
inline
JSRopeBuilder::JSRopeBuilder(JSContext *cx)
: cx(cx), mStr(cx->runtime->emptyString) {}
namespace js {
class RopeBuilder {
JSContext *cx;
JSString *res;
public:
RopeBuilder(JSContext *cx)
: cx(cx), res(cx->runtime->emptyString)
{}
inline bool append(JSString *str) {
res = js_ConcatStrings(cx, res, str);
return !!res;
}
inline JSString *result() {
return res;
}
};
class StringSegmentRange
{
/*
* If malloc() shows up in any profiles from this vector, we can add a new
* StackAllocPolicy which stashes a reusable freed-at-gc buffer in the cx.
*/
Vector<JSString *, 32> stack;
JSString *cur;
bool settle(JSString *str) {
while (str->isRope()) {
if (!stack.append(str->ropeRight()))
return false;
str = str->ropeLeft();
}
cur = str;
return true;
}
public:
StringSegmentRange(JSContext *cx)
: stack(cx), cur(NULL)
{}
JS_WARN_UNUSED_RESULT bool init(JSString *str) {
JS_ASSERT(stack.empty());
return settle(str);
}
bool empty() const {
return cur == NULL;
}
JSString *front() const {
JS_ASSERT(!cur->isRope());
return cur;
}
JS_WARN_UNUSED_RESULT bool popFront() {
JS_ASSERT(!empty());
if (stack.empty()) {
cur = NULL;
return true;
}
return settle(stack.popCopy());
}
};
} /* namespace js */
#endif /* jsstrinlines_h___ */

View File

@ -12430,7 +12430,7 @@ TraceRecorder::getCharCodeAt(JSString *str, LIns* str_ins, LIns* idx_ins, LIns**
}
guard(true,
w.ltup(idx_ins, w.rshupN(lengthAndFlags_ins, JSString::FLAGS_LENGTH_SHIFT)),
w.ltup(idx_ins, w.rshupN(lengthAndFlags_ins, JSString::LENGTH_SHIFT)),
snapshot(MISMATCH_EXIT));
*out = w.i2d(w.getStringChar(str_ins, idx_ins));
return RECORD_CONTINUE;
@ -12461,7 +12461,7 @@ TraceRecorder::getCharAt(JSString *str, LIns* str_ins, LIns* idx_ins, JSOp mode,
w.label(mbr);
}
LIns* inRange = w.ltup(idx_ins, w.rshupN(lengthAndFlags_ins, JSString::FLAGS_LENGTH_SHIFT));
LIns* inRange = w.ltup(idx_ins, w.rshupN(lengthAndFlags_ins, JSString::LENGTH_SHIFT));
if (mode == JSOP_GETELEM) {
guard(true, inRange, MISMATCH_EXIT);

View File

@ -221,6 +221,14 @@
# endif
#endif
#ifndef JS_WARN_UNUSED_RESULT
# if defined __GNUC__
# define JS_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
# else
# define JS_WARN_UNUSED_RESULT
# endif
#endif
#ifdef NS_STATIC_CHECKING
/*
* Attributes for static analysis. Functions declared with JS_REQUIRES_STACK

View File

@ -342,6 +342,8 @@ class Vector : AllocPolicy
void popBack();
T popCopy();
/*
* Transfers ownership of the internal buffer used by Vector to the caller.
* After this call, the Vector is empty. Since the returned buffer may need
@ -690,6 +692,15 @@ Vector<T,N,AP>::popBack()
endNoCheck()->~T();
}
template <class T, size_t N, class AP>
JS_ALWAYS_INLINE T
Vector<T,N,AP>::popCopy()
{
T ret = back();
popBack();
return ret;
}
template <class T, size_t N, class AP>
inline T *
Vector<T,N,AP>::extractRawBuffer()

View File

@ -2961,8 +2961,8 @@ mjit::Compiler::jsop_length()
frame.push(v);
} else {
RegisterID str = frame.ownRegForData(top);
masm.loadPtr(Address(str, offsetof(JSString, mLengthAndFlags)), str);
masm.rshiftPtr(Imm32(JSString::FLAGS_LENGTH_SHIFT), str);
masm.loadPtr(Address(str, JSString::offsetOfLengthAndFlags()), str);
masm.rshiftPtr(Imm32(JSString::LENGTH_SHIFT), str);
frame.pop();
frame.pushTypedPayload(JSVAL_TYPE_INT32, str);
}

View File

@ -292,13 +292,13 @@ class EqualityCompiler : public BaseCompiler
/* Test if lhs/rhs are atomized. */
Imm32 atomizedFlags(JSString::FLAT | JSString::ATOMIZED);
masm.load32(Address(lvr.dataReg(), offsetof(JSString, mLengthAndFlags)), tmp);
masm.load32(Address(lvr.dataReg(), JSString::offsetOfLengthAndFlags()), tmp);
masm.and32(Imm32(JSString::TYPE_FLAGS_MASK), tmp);
Jump lhsNotAtomized = masm.branch32(Assembler::NotEqual, tmp, atomizedFlags);
linkToStub(lhsNotAtomized);
if (!rvr.isConstant()) {
masm.load32(Address(rvr.dataReg(), offsetof(JSString, mLengthAndFlags)), tmp);
masm.load32(Address(rvr.dataReg(), JSString::offsetOfLengthAndFlags()), tmp);
masm.and32(Imm32(JSString::TYPE_FLAGS_MASK), tmp);
Jump rhsNotAtomized = masm.branch32(Assembler::NotEqual, tmp, atomizedFlags);
linkToStub(rhsNotAtomized);

View File

@ -977,9 +977,9 @@ class GetPropCompiler : public PICStubCompiler
Assembler masm;
Jump notString = masm.branchPtr(Assembler::NotEqual, pic.typeReg(),
ImmType(JSVAL_TYPE_STRING));
masm.loadPtr(Address(pic.objReg, offsetof(JSString, mLengthAndFlags)), pic.objReg);
masm.loadPtr(Address(pic.objReg, JSString::offsetOfLengthAndFlags()), pic.objReg);
// String length is guaranteed to be no more than 2**28, so the 32-bit operation is OK.
masm.urshift32(Imm32(JSString::FLAGS_LENGTH_SHIFT), pic.objReg);
masm.urshift32(Imm32(JSString::LENGTH_SHIFT), pic.objReg);
masm.move(ImmType(JSVAL_TYPE_INT32), pic.shapeReg);
Jump done = masm.jump();

View File

@ -484,17 +484,17 @@ void ValidateWriter::checkAccSet(LOpcode op, LIns *base, int32_t disp, AccSet ac
break;
case ACCSET_STRING_MCHARS:
// base = ldp.string ...[offsetof(JSString, mChars)]
// base = ldp.string ...[offsetof(JSString, chars)]
// ins = ldus2ui.strchars/c base[0]
// OR
// base_oprnd1 = ldp.string ...[offsetof(JSString, mChars)]
// base_oprnd1 = ldp.string ...[offsetof(JSString, chars)]
// base = addp base_oprnd1, ...
// ins = ldus2ui.strchars/c base[0]
ok = op == LIR_ldus2ui &&
disp == 0 &&
(match(base, LIR_ldp, ACCSET_STRING, offsetof(JSString, mChars)) ||
(match(base, LIR_ldp, ACCSET_STRING, JSString::offsetOfChars()) ||
(base->isop(LIR_addp) &&
match(base->oprnd1(), LIR_ldp, ACCSET_STRING, offsetof(JSString, mChars))));
match(base->oprnd1(), LIR_ldp, ACCSET_STRING, JSString::offsetOfChars())));
break;
case ACCSET_TYPEMAP:

View File

@ -598,14 +598,14 @@ class Writer
}
nj::LIns *ldpStringLengthAndFlags(nj::LIns *str) const {
return name(lir->insLoad(nj::LIR_ldp, str, offsetof(JSString, mLengthAndFlags),
return name(lir->insLoad(nj::LIR_ldp, str, JSString::offsetOfLengthAndFlags(),
ACCSET_STRING),
"mLengthAndFlags");
"lengthAndFlags");
}
nj::LIns *ldpStringChars(nj::LIns *str) const {
return name(lir->insLoad(nj::LIR_ldp, str, offsetof(JSString, mChars), ACCSET_STRING),
"mChars");
return name(lir->insLoad(nj::LIR_ldp, str, JSString::offsetOfChars(), ACCSET_STRING),
"chars");
}
nj::LIns *lduc2uiConstTypeMapEntry(nj::LIns *typemap, nj::LIns *index) const {
@ -1188,7 +1188,7 @@ class Writer
}
nj::LIns *getStringLength(nj::LIns *str) const {
return name(rshupN(ldpStringLengthAndFlags(str), JSString::FLAGS_LENGTH_SHIFT),
return name(rshupN(ldpStringLengthAndFlags(str), JSString::LENGTH_SHIFT),
"strLength");
}