- 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:
brendan%mozilla.org 2003-02-27 23:04:46 +00:00
Родитель f9119b08cb
Коммит 71041a5d9d
2 изменённых файлов: 262 добавлений и 110 удалений

Просмотреть файл

@ -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