зеркало из https://github.com/mozilla/pjs.git
- Move left-associative binary tree flattening from the post-order position
in js_FoldConstants where it was added (suboptimally: it worked, but it ran so late that js_FoldConstants recursion was not reduced, only js_EmitTree recursion), to NewBinary, where it avoids JSParseNode allocations up front and reduces recursion in all parse-tree walking. - This change enables js_FoldConstants to see a very long concatenation of string literals as a PN_LIST node, so it can quickly concatenate without running afoul of O(n^2) problems inherent in js_ConcatStrings applied to two atomized strings (the old way of folding string concatenations, still used for any pairwise string literal concatenation). - Further optimize the first change for the second by having NewBinary set a new pn_strcat flag (overlaying the pn_extra field) of the PN_LIST arm of the JSParseNode.pn_u union, whenever it sees at least one string literal in a concatenation that might be folded (whose operands might all be constants of string or number type). - Notes: - only string and number constants are folded (not boolean or null constants); - only all-constant left-associated binary expression chains are folded, so 2 * foo * 3 is not folded using commutativity of * over numbers, nor is "hi" + " there" + foo folded to "hi there" + foo. - gcc3.2 -O and objdump -x say I added 708 bytes of instructions with this change. I tried to keep it down to what was necessary for common script; I don't think JS needs an optimizing-compiler-strength constant folder, and I don't think this 1K bloat is too high a price to pay for this fix. But I'll certainly work on reducing footprint elsewhere, if I can. - Bug 174341, r=shaver.
This commit is contained in:
Родитель
f9119b08cb
Коммит
71041a5d9d
359
js/src/jsparse.c
359
js/src/jsparse.c
|
@ -165,10 +165,34 @@ NewBinary(JSContext *cx, JSTokenType tt,
|
|||
JSOp op, JSParseNode *left, JSParseNode *right,
|
||||
JSTreeContext *tc)
|
||||
{
|
||||
JSParseNode *pn;
|
||||
JSParseNode *pn, *pn1, *pn2;
|
||||
|
||||
if (!left || !right)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* Flatten a left-associative (left-heavy) tree of a given operator into
|
||||
* a list, to reduce js_FoldConstants and js_EmitTree recursion.
|
||||
*/
|
||||
if (left->pn_type == tt &&
|
||||
left->pn_op == op &&
|
||||
(js_CodeSpec[op].format & JOF_LEFTASSOC)) {
|
||||
if (left->pn_arity != PN_LIST) {
|
||||
pn1 = left->pn_left, pn2 = left->pn_right;
|
||||
left->pn_arity = PN_LIST;
|
||||
PN_INIT_LIST_1(left, pn1);
|
||||
PN_APPEND(left, pn2);
|
||||
left->pn_strcat = (tt == TOK_PLUS &&
|
||||
(pn1->pn_type == TOK_STRING ||
|
||||
pn2->pn_type == TOK_STRING));
|
||||
}
|
||||
PN_APPEND(left, right);
|
||||
left->pn_pos.end = right->pn_pos.end;
|
||||
if (tt == TOK_PLUS && right->pn_type == TOK_STRING)
|
||||
left->pn_strcat = JS_TRUE;
|
||||
return left;
|
||||
}
|
||||
|
||||
pn = tc->nodeList;
|
||||
if (pn) {
|
||||
tc->nodeList = pn->pn_next;
|
||||
|
@ -3034,6 +3058,139 @@ ContainsVarStmt(JSParseNode *pn)
|
|||
return JS_FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fold from one constant type to another.
|
||||
* XXX handles only strings and numbers for now
|
||||
*/
|
||||
static JSBool
|
||||
FoldType(JSContext *cx, JSParseNode *pn, JSTokenType type)
|
||||
{
|
||||
if (pn->pn_type != type) {
|
||||
switch (type) {
|
||||
case TOK_NUMBER:
|
||||
if (pn->pn_type == TOK_STRING) {
|
||||
jsdouble d;
|
||||
if (!js_ValueToNumber(cx, ATOM_KEY(pn->pn_atom), &d))
|
||||
return JS_FALSE;
|
||||
pn->pn_dval = d;
|
||||
pn->pn_type = TOK_NUMBER;
|
||||
pn->pn_op = JSOP_NUMBER;
|
||||
}
|
||||
break;
|
||||
|
||||
case TOK_STRING:
|
||||
if (pn->pn_type == TOK_NUMBER) {
|
||||
JSString *str = js_NumberToString(cx, pn->pn_dval);
|
||||
if (!str)
|
||||
return JS_FALSE;
|
||||
pn->pn_atom = js_AtomizeString(cx, str, 0);
|
||||
if (!pn->pn_atom)
|
||||
return JS_FALSE;
|
||||
pn->pn_type = TOK_STRING;
|
||||
pn->pn_op = JSOP_STRING;
|
||||
}
|
||||
break;
|
||||
|
||||
default:;
|
||||
}
|
||||
}
|
||||
return JS_TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fold two numeric constants. Beware that pn1 and pn2 are recycled, unless
|
||||
* one of them aliases pn, so you can't safely fetch pn2->pn_next, e.g., after
|
||||
* a successful call to this function.
|
||||
*/
|
||||
static JSBool
|
||||
FoldBinaryNumeric(JSContext *cx, JSOp op, JSParseNode *pn1, JSParseNode *pn2,
|
||||
JSParseNode *pn, JSTreeContext *tc)
|
||||
{
|
||||
jsdouble d, d2;
|
||||
int32 i, j;
|
||||
uint32 u;
|
||||
|
||||
JS_ASSERT(pn1->pn_type == TOK_NUMBER && pn2->pn_type == TOK_NUMBER);
|
||||
d = pn1->pn_dval;
|
||||
d2 = pn2->pn_dval;
|
||||
switch (op) {
|
||||
case JSOP_LSH:
|
||||
case JSOP_RSH:
|
||||
if (!js_DoubleToECMAInt32(cx, d, &i))
|
||||
return JS_FALSE;
|
||||
if (!js_DoubleToECMAInt32(cx, d2, &j))
|
||||
return JS_FALSE;
|
||||
j &= 31;
|
||||
d = (op == JSOP_LSH) ? i << j : i >> j;
|
||||
break;
|
||||
|
||||
case JSOP_URSH:
|
||||
if (!js_DoubleToECMAUint32(cx, d, &u))
|
||||
return JS_FALSE;
|
||||
if (!js_DoubleToECMAInt32(cx, d2, &j))
|
||||
return JS_FALSE;
|
||||
j &= 31;
|
||||
d = u >> j;
|
||||
break;
|
||||
|
||||
case JSOP_ADD:
|
||||
d += d2;
|
||||
break;
|
||||
|
||||
case JSOP_SUB:
|
||||
d -= d2;
|
||||
break;
|
||||
|
||||
case JSOP_MUL:
|
||||
d *= d2;
|
||||
break;
|
||||
|
||||
case JSOP_DIV:
|
||||
if (d2 == 0) {
|
||||
#ifdef XP_PC
|
||||
/* XXX MSVC miscompiles such that (NaN == 0) */
|
||||
if (JSDOUBLE_IS_NaN(d2))
|
||||
d = *cx->runtime->jsNaN;
|
||||
else
|
||||
#endif
|
||||
if (d == 0 || JSDOUBLE_IS_NaN(d))
|
||||
d = *cx->runtime->jsNaN;
|
||||
else if ((JSDOUBLE_HI32(d) ^ JSDOUBLE_HI32(d2)) >> 31)
|
||||
d = *cx->runtime->jsNegativeInfinity;
|
||||
else
|
||||
d = *cx->runtime->jsPositiveInfinity;
|
||||
} else {
|
||||
d /= d2;
|
||||
}
|
||||
break;
|
||||
|
||||
case JSOP_MOD:
|
||||
if (d2 == 0) {
|
||||
d = *cx->runtime->jsNaN;
|
||||
} else {
|
||||
#ifdef XP_PC
|
||||
/* Workaround MS fmod bug where 42 % (1/0) => NaN, not 42. */
|
||||
if (!(JSDOUBLE_IS_FINITE(d) && JSDOUBLE_IS_INFINITE(d2)))
|
||||
#endif
|
||||
d = fmod(d, d2);
|
||||
}
|
||||
break;
|
||||
|
||||
default:;
|
||||
}
|
||||
|
||||
/* Take care to allow pn1 or pn2 to alias pn. */
|
||||
if (pn1 != pn)
|
||||
RecycleTree(pn1, tc);
|
||||
if (pn2 != pn)
|
||||
RecycleTree(pn2, tc);
|
||||
pn->pn_type = TOK_NUMBER;
|
||||
pn->pn_op = JSOP_NUMBER;
|
||||
pn->pn_arity = PN_NULLARY;
|
||||
pn->pn_dval = d;
|
||||
return JS_TRUE;
|
||||
}
|
||||
|
||||
JSBool
|
||||
js_FoldConstants(JSContext *cx, JSParseNode *pn, JSTreeContext *tc)
|
||||
{
|
||||
|
@ -3046,7 +3203,8 @@ js_FoldConstants(JSContext *cx, JSParseNode *pn, JSTreeContext *tc)
|
|||
break;
|
||||
|
||||
case PN_LIST:
|
||||
for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
|
||||
/* Save the list head in pn1 for later use. */
|
||||
for (pn1 = pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
|
||||
if (!js_FoldConstants(cx, pn2, tc))
|
||||
return JS_FALSE;
|
||||
}
|
||||
|
@ -3145,9 +3303,69 @@ js_FoldConstants(JSContext *cx, JSParseNode *pn, JSTreeContext *tc)
|
|||
break;
|
||||
|
||||
case TOK_PLUS:
|
||||
if (pn1->pn_type == TOK_STRING && pn2->pn_type == TOK_STRING) {
|
||||
if (pn->pn_arity == PN_LIST) {
|
||||
size_t length, length2;
|
||||
jschar *chars;
|
||||
JSString *str, *str2;
|
||||
|
||||
/* Any one string literal operand means this is a concatenation. */
|
||||
JS_ASSERT(pn->pn_count > 2);
|
||||
if (!pn->pn_strcat)
|
||||
goto do_binary_op;
|
||||
|
||||
/* Ok, we're concatenating: convert non-string constant operands. */
|
||||
length = 0;
|
||||
for (pn2 = pn1; pn2; pn2 = pn2->pn_next) {
|
||||
if (!FoldType(cx, pn2, TOK_STRING))
|
||||
return JS_FALSE;
|
||||
/* XXX fold only if all operands convert to string */
|
||||
if (pn2->pn_type != TOK_STRING)
|
||||
return JS_TRUE;
|
||||
length += ATOM_TO_STRING(pn2->pn_atom)->length;
|
||||
}
|
||||
|
||||
/* Allocate a new buffer and string descriptor for the result. */
|
||||
chars = (jschar *) JS_malloc(cx, (length + 1) * sizeof(jschar));
|
||||
if (!chars)
|
||||
return JS_FALSE;
|
||||
str = js_NewString(cx, chars, length, 0);
|
||||
if (!str) {
|
||||
JS_free(cx, chars);
|
||||
return JS_FALSE;
|
||||
}
|
||||
|
||||
/* Fill the buffer, advancing chars and recycling kids as we go. */
|
||||
for (pn2 = pn1; pn2; pn2 = pn3) {
|
||||
str2 = ATOM_TO_STRING(pn2->pn_atom);
|
||||
length2 = str2->length;
|
||||
js_strncpy(chars, str2->chars, length2);
|
||||
chars += length2;
|
||||
pn3 = pn2->pn_next;
|
||||
RecycleTree(pn2, tc);
|
||||
}
|
||||
*chars = 0;
|
||||
|
||||
/* Atomize the result string and mutate pn to refer to it. */
|
||||
pn->pn_atom = js_AtomizeString(cx, str, 0);
|
||||
if (!pn->pn_atom)
|
||||
return JS_FALSE;
|
||||
pn->pn_type = TOK_STRING;
|
||||
pn->pn_op = JSOP_STRING;
|
||||
pn->pn_arity = PN_NULLARY;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Handle a binary string concatenation. */
|
||||
JS_ASSERT(pn->pn_arity == PN_BINARY);
|
||||
if (pn1->pn_type == TOK_STRING || pn2->pn_type == TOK_STRING) {
|
||||
JSString *left, *right, *str;
|
||||
|
||||
if (!FoldType(cx, (pn1->pn_type != TOK_STRING) ? pn1 : pn2,
|
||||
TOK_STRING)) {
|
||||
return JS_FALSE;
|
||||
}
|
||||
if (pn1->pn_type != TOK_STRING || pn2->pn_type != TOK_STRING)
|
||||
return JS_TRUE;
|
||||
left = ATOM_TO_STRING(pn1->pn_atom);
|
||||
right = ATOM_TO_STRING(pn2->pn_atom);
|
||||
str = js_ConcatStrings(cx, left, right);
|
||||
|
@ -3163,7 +3381,9 @@ js_FoldConstants(JSContext *cx, JSParseNode *pn, JSTreeContext *tc)
|
|||
RecycleTree(pn2, tc);
|
||||
break;
|
||||
}
|
||||
goto do_binary;
|
||||
|
||||
/* Can't concatenate string literals, let's try numbers. */
|
||||
goto do_binary_op;
|
||||
|
||||
case TOK_STAR:
|
||||
/* The * in 'import *;' parses as a nullary star node. */
|
||||
|
@ -3174,86 +3394,35 @@ js_FoldConstants(JSContext *cx, JSParseNode *pn, JSTreeContext *tc)
|
|||
case TOK_SHOP:
|
||||
case TOK_MINUS:
|
||||
case TOK_DIVOP:
|
||||
do_binary:
|
||||
if (pn1->pn_type == TOK_NUMBER && pn2->pn_type == TOK_NUMBER) {
|
||||
jsdouble d, d2;
|
||||
int32 i, j;
|
||||
uint32 u;
|
||||
|
||||
/* Fold two numeric constants. */
|
||||
d = pn1->pn_dval;
|
||||
d2 = pn2->pn_dval;
|
||||
switch (pn->pn_op) {
|
||||
case JSOP_LSH:
|
||||
case JSOP_RSH:
|
||||
if (!js_DoubleToECMAInt32(cx, d, &i))
|
||||
do_binary_op:
|
||||
if (pn->pn_arity == PN_LIST) {
|
||||
JS_ASSERT(pn->pn_count > 2);
|
||||
for (pn2 = pn1; pn2; pn2 = pn2->pn_next) {
|
||||
if (!FoldType(cx, pn2, TOK_NUMBER))
|
||||
return JS_FALSE;
|
||||
/* XXX fold only if all operands convert to number */
|
||||
if (pn2->pn_type != TOK_NUMBER)
|
||||
break;
|
||||
}
|
||||
if (!pn2) {
|
||||
JSOp op = pn->pn_op;
|
||||
|
||||
pn2 = pn1->pn_next;
|
||||
pn3 = pn2->pn_next;
|
||||
if (!FoldBinaryNumeric(cx, op, pn1, pn2, pn, tc))
|
||||
return JS_FALSE;
|
||||
while ((pn2 = pn3) != NULL) {
|
||||
pn3 = pn2->pn_next;
|
||||
if (!FoldBinaryNumeric(cx, op, pn, pn2, pn, tc))
|
||||
return JS_FALSE;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
JS_ASSERT(pn->pn_arity == PN_BINARY);
|
||||
if (pn1->pn_type == TOK_NUMBER && pn2->pn_type == TOK_NUMBER) {
|
||||
if (!FoldBinaryNumeric(cx, pn->pn_op, pn1, pn2, pn, tc))
|
||||
return JS_FALSE;
|
||||
if (!js_DoubleToECMAInt32(cx, d2, &j))
|
||||
return JS_FALSE;
|
||||
j &= 31;
|
||||
d = (pn->pn_op == JSOP_LSH) ? i << j : i >> j;
|
||||
break;
|
||||
|
||||
case JSOP_URSH:
|
||||
if (!js_DoubleToECMAUint32(cx, d, &u))
|
||||
return JS_FALSE;
|
||||
if (!js_DoubleToECMAInt32(cx, d2, &j))
|
||||
return JS_FALSE;
|
||||
j &= 31;
|
||||
d = u >> j;
|
||||
break;
|
||||
|
||||
case JSOP_ADD:
|
||||
d += d2;
|
||||
break;
|
||||
|
||||
case JSOP_SUB:
|
||||
d -= d2;
|
||||
break;
|
||||
|
||||
case JSOP_MUL:
|
||||
d *= d2;
|
||||
break;
|
||||
|
||||
case JSOP_DIV:
|
||||
if (d2 == 0) {
|
||||
#ifdef XP_PC
|
||||
/* XXX MSVC miscompiles such that (NaN == 0) */
|
||||
if (JSDOUBLE_IS_NaN(d2))
|
||||
d = *cx->runtime->jsNaN;
|
||||
else
|
||||
#endif
|
||||
if (d == 0 || JSDOUBLE_IS_NaN(d))
|
||||
d = *cx->runtime->jsNaN;
|
||||
else if ((JSDOUBLE_HI32(d) ^ JSDOUBLE_HI32(d2)) >> 31)
|
||||
d = *cx->runtime->jsNegativeInfinity;
|
||||
else
|
||||
d = *cx->runtime->jsPositiveInfinity;
|
||||
} else {
|
||||
d /= d2;
|
||||
}
|
||||
break;
|
||||
|
||||
case JSOP_MOD:
|
||||
if (d2 == 0) {
|
||||
d = *cx->runtime->jsNaN;
|
||||
} else {
|
||||
#ifdef XP_PC
|
||||
/* Workaround MS fmod bug where 42 % (1/0) => NaN, not 42. */
|
||||
if (!(JSDOUBLE_IS_FINITE(d) && JSDOUBLE_IS_INFINITE(d2)))
|
||||
#endif
|
||||
d = fmod(d, d2);
|
||||
}
|
||||
break;
|
||||
|
||||
default:;
|
||||
}
|
||||
pn->pn_type = TOK_NUMBER;
|
||||
pn->pn_op = JSOP_NUMBER;
|
||||
pn->pn_arity = PN_NULLARY;
|
||||
pn->pn_dval = d;
|
||||
RecycleTree(pn1, tc);
|
||||
RecycleTree(pn2, tc);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -3308,31 +3477,5 @@ js_FoldConstants(JSContext *cx, JSParseNode *pn, JSTreeContext *tc)
|
|||
default:;
|
||||
}
|
||||
|
||||
/*
|
||||
* Flatten a left-associative (left-heavy) tree of a given operator into
|
||||
* a list, to reduce js_EmitTree recursion.
|
||||
*/
|
||||
if (pn->pn_arity == PN_BINARY &&
|
||||
pn1 &&
|
||||
pn1->pn_type == pn->pn_type &&
|
||||
pn1->pn_op == pn->pn_op &&
|
||||
(js_CodeSpec[pn->pn_op].format & JOF_LEFTASSOC)) {
|
||||
if (pn1->pn_arity == PN_LIST) {
|
||||
/* We already flattened pn1, so move it to pn and append pn2. */
|
||||
PN_MOVE_NODE(pn, pn1);
|
||||
PN_APPEND(pn, pn2);
|
||||
} else {
|
||||
/* Convert pn into a list containing pn1's kids followed by pn2. */
|
||||
pn->pn_arity = PN_LIST;
|
||||
PN_INIT_LIST_1(pn, pn1->pn_left);
|
||||
PN_APPEND(pn, pn1->pn_right);
|
||||
PN_APPEND(pn, pn2);
|
||||
|
||||
/* Clear pn1 so it can be recycled by itself, without its kids. */
|
||||
PN_CLEAR_NODE(pn1);
|
||||
}
|
||||
RecycleTree(pn1, tc);
|
||||
}
|
||||
|
||||
return JS_TRUE;
|
||||
}
|
||||
|
|
|
@ -102,13 +102,16 @@ JS_BEGIN_EXTERN_C
|
|||
* TOK_CONTINUE name pn_atom: label or null
|
||||
* TOK_WITH binary pn_left: head expr, pn_right: body
|
||||
* TOK_VAR list pn_head: list of pn_count TOK_NAME nodes
|
||||
* each name node has pn_atom: variable name and
|
||||
* pn_expr: initializer or null
|
||||
* each name node has
|
||||
* pn_atom: variable name
|
||||
* pn_expr: initializer or null
|
||||
* TOK_RETURN unary pn_kid: return expr or null
|
||||
* TOK_SEMI unary pn_kid: expr or null statement
|
||||
* TOK_COLON name pn_atom: label, pn_expr: labeled statement
|
||||
*
|
||||
* <Expressions>
|
||||
* All left-associated binary trees of the same type are optimized into lists
|
||||
* to avoid recursion when processing expression chains.
|
||||
* TOK_COMMA list pn_head: list of pn_count comma-separated exprs
|
||||
* TOK_ASSIGN binary pn_left: lvalue, pn_right: rvalue
|
||||
* pn_op: JSOP_ADD for +=, etc.
|
||||
|
@ -125,6 +128,11 @@ JS_BEGIN_EXTERN_C
|
|||
* TOK_SHOP binary pn_left: left-assoc SH expr, pn_right: ADD expr
|
||||
* pn_op: JSOP_LSH, JSOP_RSH, JSOP_URSH
|
||||
* TOK_PLUS, binary pn_left: left-assoc ADD expr, pn_right: MUL expr
|
||||
* pn_strcat: if a left-associated binary TOK_PLUS
|
||||
* tree has been flattened into a list (see above
|
||||
* under <Expressions>), pn_strcat will be true if
|
||||
* at least one list element is a string literal
|
||||
* (TOK_STRING), and false otherwise.
|
||||
* TOK_MINUS pn_op: JSOP_ADD, JSOP_SUB
|
||||
* TOK_STAR, binary pn_left: left-assoc MUL expr, pn_right: UNARY expr
|
||||
* TOK_DIVOP pn_op: JSOP_MUL, JSOP_DIV, JSOP_MOD
|
||||
|
@ -225,6 +233,7 @@ struct JSParseNode {
|
|||
#define pn_tail pn_u.list.tail
|
||||
#define pn_count pn_u.list.count
|
||||
#define pn_extra pn_u.list.extra
|
||||
#define pn_strcat pn_u.list.extra
|
||||
#define pn_kid1 pn_u.ternary.kid1
|
||||
#define pn_kid2 pn_u.ternary.kid2
|
||||
#define pn_kid3 pn_u.ternary.kid3
|
||||
|
|
Загрузка…
Ссылка в новой задаче