mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-29 07:42:04 +00:00
Bug 609440, part 2 - do lazy allocation, dag-ify ropes (assume no oom) (r=njn)
This commit is contained in:
parent
d9a9bbca84
commit
b53039418b
@ -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)
|
||||
|
@ -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
|
||||
|
485
js/src/jsstr.cpp
485
js/src/jsstr.cpp
@ -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} } }
|
||||
|
||||
|
||||
|
463
js/src/jsstr.h
463
js/src/jsstr.h
@ -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);
|
||||
|
@ -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___ */
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user