diff --git a/js/src/jsparse.c b/js/src/jsparse.c index 2a3e4fb5d71..53ecd9494d4 100644 --- a/js/src/jsparse.c +++ b/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; } diff --git a/js/src/jsparse.h b/js/src/jsparse.h index 42fa29bc0bd..bc00975878a 100644 --- a/js/src/jsparse.h +++ b/js/src/jsparse.h @@ -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 * * + * 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 ), 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