Optimize static RegExp state (560358, r=brendan).

This commit is contained in:
Andreas Gal 2010-04-28 13:33:21 -07:00
parent 74b496bbe8
commit 6fe4800714
6 changed files with 80 additions and 129 deletions

View File

@ -5515,19 +5515,8 @@ JS_SetRegExpInput(JSContext *cx, JSString *input, JSBool multiline)
JS_PUBLIC_API(void)
JS_ClearRegExpStatics(JSContext *cx)
{
JSRegExpStatics *res;
/* No locking required, cx is thread-private and input must be live. */
res = &cx->regExpStatics;
res->input = NULL;
res->multiline = JS_FALSE;
res->parenCount = 0;
res->lastMatch = res->lastParen = js_EmptySubString;
res->leftContext = res->rightContext = js_EmptySubString;
if (res->moreParens) {
cx->free(res->moreParens);
res->moreParens = NULL;
}
cx->regExpStatics.clear();
cx->runtime->gcPoke = JS_TRUE;
}

View File

@ -1178,9 +1178,42 @@ namespace js {
class AutoGCRooter;
}
struct JSRegExpStatics {
JSString *input; /* input string to match (perl $_, GC root) */
JSBool multiline; /* whether input contains newlines (perl $*) */
JSSubString lastMatch; /* last string matched (perl $&) */
JSSubString lastParen; /* last paren matched (perl $+) */
JSSubString leftContext; /* input to left of last match (perl $`) */
JSSubString rightContext; /* input to right of last match (perl $') */
js::Vector<JSSubString> parens; /* last set of parens matched (perl $1, $2) */
JSRegExpStatics(JSContext *cx) : parens(cx) {}
bool copy(const JSRegExpStatics& other) {
input = other.input;
multiline = other.multiline;
lastMatch = other.lastMatch;
lastParen = other.lastParen;
leftContext = other.leftContext;
rightContext = other.rightContext;
if (!parens.resize(other.parens.length()))
return false;
memcpy(parens.begin(), other.parens.begin(), sizeof(JSSubString) * parens.length());
return true;
}
void clear() {
input = NULL;
multiline = false;
lastMatch = lastParen = leftContext = rightContext = js_EmptySubString;
parens.clear();
}
};
struct JSContext
{
explicit JSContext(JSRuntime *rt) : runtime(rt), busyArrays(this) {}
explicit JSContext(JSRuntime *rt) :
runtime(rt), regExpStatics(this), busyArrays(this) {}
/*
* If this flag is set, we were asked to call back the operation callback
@ -1266,7 +1299,7 @@ struct JSContext
/* Storage to root recently allocated GC things and script result. */
JSWeakRoots weakRoots;
/* Regular expression class statics (XXX not shared globally). */
/* Regular expression class statics. */
JSRegExpStatics regExpStatics;
/* State for object and array toSource conversion. */

View File

@ -4865,11 +4865,10 @@ js_ExecuteRegExp(JSContext *cx, JSRegExp *re, JSString *str, size_t *indexp,
const jschar *cp, *ep;
size_t i, length, start;
JSSubString *morepar;
JSBool ok;
JSRegExpStatics *res;
ptrdiff_t matchlen;
uintN num, morenum;
uintN num;
JSString *parstr, *matchstr;
JSObject *obj;
@ -4973,45 +4972,22 @@ js_ExecuteRegExp(JSContext *cx, JSRegExp *re, JSString *str, size_t *indexp,
res = &cx->regExpStatics;
res->input = str;
res->parenCount = uint16(re->parenCount);
if (!res->parens.resize(re->parenCount)) {
ok = JS_FALSE;
goto out;
}
if (re->parenCount == 0) {
res->lastParen = js_EmptySubString;
} else {
for (num = 0; num < re->parenCount; num++) {
JSSubString *sub = &res->parens[num];
parsub = &result->parens[num];
if (num < 9) {
if (parsub->index == -1) {
res->parens[num].chars = NULL;
res->parens[num].length = 0;
} else {
res->parens[num].chars = gData.cpbegin + parsub->index;
res->parens[num].length = parsub->length;
}
if (parsub->index == -1) {
sub->chars = NULL;
sub->length = 0;
} else {
morenum = num - 9;
morepar = res->moreParens;
if (!morepar) {
res->moreLength = 10;
morepar = (JSSubString*)
cx->malloc(10 * sizeof(JSSubString));
} else if (morenum >= res->moreLength) {
res->moreLength += 10;
morepar = (JSSubString*)
cx->realloc(morepar,
res->moreLength * sizeof(JSSubString));
}
if (!morepar) {
ok = JS_FALSE;
goto out;
}
res->moreParens = morepar;
if (parsub->index == -1) {
morepar[morenum].chars = NULL;
morepar[morenum].length = 0;
} else {
morepar[morenum].chars = gData.cpbegin + parsub->index;
morepar[morenum].length = parsub->length;
}
sub->chars = gData.cpbegin + parsub->index;
sub->length = parsub->length;
}
if (test)
continue;
@ -5209,14 +5185,9 @@ JS_FRIEND_API(void)
js_SaveAndClearRegExpStatics(JSContext *cx, JSRegExpStatics *statics,
AutoValueRooter *tvr)
{
*statics = cx->regExpStatics;
statics->copy(cx->regExpStatics);
if (statics->input)
tvr->setString(statics->input);
/*
* Prevent JS_ClearRegExpStatics from freeing moreParens, since we've only
* moved it elsewhere (into statics->moreParens).
*/
cx->regExpStatics.moreParens = NULL;
JS_ClearRegExpStatics(cx);
}
@ -5225,8 +5196,7 @@ js_RestoreRegExpStatics(JSContext *cx, JSRegExpStatics *statics,
AutoValueRooter *tvr)
{
/* Clear/free any new JSRegExpStatics data before clobbering. */
JS_ClearRegExpStatics(cx);
cx->regExpStatics = *statics;
cx->regExpStatics.copy(*statics);
}
void
@ -5278,7 +5248,7 @@ regexp_static_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
sub = &res->rightContext;
break;
default:
sub = REGEXP_PAREN_SUBSTRING(res, slot);
sub = (size_t(slot) < res->parens.length()) ? &res->parens[slot] : &js_EmptySubString;
break;
}
str = js_NewStringCopyN(cx, sub->chars, sub->length);

View File

@ -52,19 +52,6 @@
JS_BEGIN_EXTERN_C
struct JSRegExpStatics {
JSString *input; /* input string to match (perl $_, GC root) */
JSBool multiline; /* whether input contains newlines (perl $*) */
uint16 parenCount; /* number of valid elements in parens[] */
uint16 moreLength; /* number of allocated elements in moreParens */
JSSubString parens[9]; /* last set of parens matched (perl $1, $2) */
JSSubString *moreParens; /* null or realloc'd vector for $10, etc. */
JSSubString lastMatch; /* last string matched (perl $&) */
JSSubString lastParen; /* last paren matched (perl $+) */
JSSubString leftContext; /* input to left of last match (perl $`) */
JSSubString rightContext; /* input to right of last match (perl $') */
};
namespace js { class AutoValueRooter; }
extern JS_FRIEND_API(void)
@ -96,17 +83,6 @@ typedef struct RECharSet {
} u;
} RECharSet;
/*
* This macro is safe because moreParens is guaranteed to be allocated and big
* enough to hold parenCount, or else be null when parenCount is 0.
*/
#define REGEXP_PAREN_SUBSTRING(res, num) \
(((jsuint)(num) < (jsuint)(res)->parenCount) \
? ((jsuint)(num) < 9) \
? &(res)->parens[num] \
: &(res)->moreParens[(num) - 9] \
: &js_EmptySubString)
typedef struct RENode RENode;
struct JSRegExp {

View File

@ -1719,13 +1719,13 @@ InterpretDollar(JSContext *cx, jschar *dp, jschar *ep, ReplaceData &rdata,
if (JS7_ISDEC(dc)) {
/* ECMA-262 Edition 3: 1-9 or 01-99 */
num = JS7_UNDEC(dc);
if (num > res->parenCount)
if (num > res->parens.length())
return NULL;
cp = dp + 2;
if (cp < ep && (dc = *cp, JS7_ISDEC(dc))) {
tmp = 10 * num + JS7_UNDEC(dc);
if (tmp <= res->parenCount) {
if (tmp <= res->parens.length()) {
cp++;
num = tmp;
}
@ -1736,7 +1736,7 @@ InterpretDollar(JSContext *cx, jschar *dp, jschar *ep, ReplaceData &rdata,
/* Adjust num from 1 $n-origin to 0 array-index-origin. */
num--;
*skip = cp - dp;
return REGEXP_PAREN_SUBSTRING(res, num);
return (num < res->parens.length()) ? &res->parens[num] : &js_EmptySubString;
}
*skip = 2;
@ -1769,6 +1769,20 @@ PushRegExpSubstr(JSContext *cx, const JSSubString &sub, jsval *&sp)
return true;
}
class PreserveRegExpStatics {
JSContext *cx;
JSRegExpStatics save;
public:
PreserveRegExpStatics(JSContext *cx) : cx(cx), save(cx) {
save.copy(cx->regExpStatics);
}
~PreserveRegExpStatics() {
cx->regExpStatics.copy(save);
}
};
static bool
FindReplaceLength(JSContext *cx, ReplaceData &rdata, size_t *sizep)
{
@ -1780,8 +1794,6 @@ FindReplaceLength(JSContext *cx, ReplaceData &rdata, size_t *sizep)
lambda = rdata.lambda;
if (lambda) {
uintN i, m, n;
LeaveTrace(cx);
/*
@ -1802,17 +1814,7 @@ FindReplaceLength(JSContext *cx, ReplaceData &rdata, size_t *sizep)
}
jsval* invokevp = rdata.invokevp;
MUST_FLOW_THROUGH("lambda_out");
bool ok = false;
bool freeMoreParens = false;
/*
* Save the regExpStatics from the current regexp, since they may be
* clobbered by a RegExp usage in the lambda function. Note that all
* members of JSRegExpStatics are JSSubStrings, so not GC roots, save
* input, which is rooted otherwise via vp[1] in str_replace.
*/
JSRegExpStatics save = cx->regExpStatics;
PreserveRegExpStatics save(cx);
/* Push lambda and its 'this' parameter. */
jsval *sp = invokevp;
@ -1821,27 +1823,13 @@ FindReplaceLength(JSContext *cx, ReplaceData &rdata, size_t *sizep)
/* Push $&, $1, $2, ... */
if (!PushRegExpSubstr(cx, cx->regExpStatics.lastMatch, sp))
goto lambda_out;
return false;
i = 0;
m = cx->regExpStatics.parenCount;
n = JS_MIN(m, 9);
for (uintN j = 0; i < n; i++, j++) {
if (!PushRegExpSubstr(cx, cx->regExpStatics.parens[j], sp))
goto lambda_out;
uintN i = 0;
for (uintN n = cx->regExpStatics.parens.length(); i < n; i++) {
if (!PushRegExpSubstr(cx, cx->regExpStatics.parens[i], sp))
return false;
}
for (uintN j = 0; i < m; i++, j++) {
if (!PushRegExpSubstr(cx, cx->regExpStatics.moreParens[j], sp))
goto lambda_out;
}
/*
* We need to clear moreParens in the top-of-stack cx->regExpStatics
* so it won't be possibly realloc'ed, leaving the bottom-of-stack
* moreParens pointing to freed memory.
*/
cx->regExpStatics.moreParens = NULL;
freeMoreParens = true;
/* Make sure to push undefined for any unmatched parens. */
for (; i < p; i++)
@ -1852,7 +1840,7 @@ FindReplaceLength(JSContext *cx, ReplaceData &rdata, size_t *sizep)
*sp++ = STRING_TO_JSVAL(rdata.str);
if (!js_Invoke(cx, argc, invokevp, 0))
goto lambda_out;
return false;
/*
* NB: we count on the newborn string root to hold any string
@ -1861,18 +1849,12 @@ FindReplaceLength(JSContext *cx, ReplaceData &rdata, size_t *sizep)
*/
repstr = js_ValueToString(cx, *invokevp);
if (!repstr)
goto lambda_out;
return false;
rdata.repstr = repstr;
*sizep = repstr->length();
ok = true;
lambda_out:
if (freeMoreParens)
cx->free(cx->regExpStatics.moreParens);
cx->regExpStatics = save;
return ok;
return true;
}
repstr = rdata.repstr;
@ -2212,10 +2194,11 @@ str_split(JSContext *cx, uintN argc, jsval *vp)
* substring that was delimited.
*/
if (re && sep->chars) {
for (uintN num = 0; num < cx->regExpStatics.parenCount; num++) {
JSRegExpStatics *res = &cx->regExpStatics;
for (uintN num = 0; num < res->parens.length(); num++) {
if (limited && len >= limit)
break;
JSSubString *parsub = REGEXP_PAREN_SUBSTRING(&cx->regExpStatics, num);
JSSubString *parsub = &res->parens[num];
sub = js_NewStringCopyN(cx, parsub->chars, parsub->length);
if (!sub || !splits.push(sub))
return false;

View File

@ -534,7 +534,7 @@ XPC_SJOW_DelProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
NS_STACK_CLASS class SafeCallGuard {
public:
SafeCallGuard(JSContext *cx, nsIPrincipal *principal)
: cx(cx), tvr(cx) {
: cx(cx), statics(cx), tvr(cx) {
nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager();
if (ssm) {
// Note: We pass null as the target frame pointer because we know that