diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index 9c9cdfb2194..8be778a1ba3 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -746,10 +746,9 @@ js_GetDenseArrayElementValue(JSObject *obj, JSProperty *prop) JS_ASSERT(OBJ_IS_DENSE_ARRAY(cx, obj)); JS_ASSERT((void *) prop == (void *) &(obj->fslots[JSSLOT_ARRAY_LOOKUP_HOLDER])); - JS_ASSERT((jsval) prop->id == obj->fslots[JSSLOT_ARRAY_LOOKUP_HOLDER]); - JS_ASSERT(JSVAL_IS_INT(prop->id)); + JS_ASSERT(JSVAL_IS_INT(obj->fslots[JSSLOT_ARRAY_LOOKUP_HOLDER])); - jsint i = JSID_TO_INT(prop->id); + jsint i = JSVAL_TO_INT(obj->fslots[JSSLOT_ARRAY_LOOKUP_HOLDER]); JS_ASSERT(i >= 0); jsval v = obj->dslots[i]; JS_ASSERT(v != JSVAL_HOLE); diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index 6d07b7fb1c7..98e9666ea85 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -1557,7 +1557,7 @@ js_ReportValueErrorFlags(JSContext *cx, uintN flags, const uintN errorNumber, #if defined DEBUG && defined XP_UNIX /* For gdb usage. */ -void js_traceon(JSContext *cx) { cx->tracefp = stderr; } +void js_traceon(JSContext *cx) { cx->tracefp = stderr; cx->tracePrevOp = JSOP_LIMIT; } void js_traceoff(JSContext *cx) { cx->tracefp = NULL; } #endif diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 27a290177ec..05ed2aca1fd 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -922,6 +922,7 @@ struct JSContext { char *lastMessage; #ifdef DEBUG void *tracefp; + JSOp tracePrevOp; #endif /* Per-context optional error reporter. */ diff --git a/js/src/jsemit.cpp b/js/src/jsemit.cpp index 24441ca77e5..cf3f278492c 100644 --- a/js/src/jsemit.cpp +++ b/js/src/jsemit.cpp @@ -3449,7 +3449,10 @@ EmitDestructuringOpsHelper(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { /* * Duplicate the value being destructured to use as a reference base. + * If dup is not the first one, annotate it for the decompiler. */ + if (pn2 != pn->pn_head && js_NewSrcNote(cx, cg, SRC_CONTINUE) < 0) + return JS_FALSE; if (js_Emit1(cx, cg, JSOP_DUP) < 0) return JS_FALSE; @@ -3571,7 +3574,6 @@ EmitGroupAssignment(JSContext *cx, JSCodeGenerator *cg, JSOp declOp, if (js_Emit1(cx, cg, JSOP_PUSH) < 0) return JS_FALSE; } else { - JS_ASSERT_IF(pn->pn_type == TOK_DEFSHARP, pn->pn_kid); if (!js_EmitTree(cx, cg, pn)) return JS_FALSE; } @@ -3625,9 +3627,7 @@ MaybeEmitGroupAssignment(JSContext *cx, JSCodeGenerator *cg, JSOp declOp, lhs = pn->pn_left; rhs = pn->pn_right; if (lhs->pn_type == TOK_RB && rhs->pn_type == TOK_RB && - lhs->pn_count <= rhs->pn_count && - (rhs->pn_count == 0 || - rhs->pn_head->pn_type != TOK_DEFSHARP)) { + lhs->pn_count <= rhs->pn_count) { if (!EmitGroupAssignment(cx, cg, declOp, lhs, rhs)) return JS_FALSE; *pop = JSOP_NOP; @@ -3916,6 +3916,9 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) JSOp op; JSTokenType type; uint32 argc; +#if JS_HAS_SHARP_VARS + jsint sharpnum; +#endif JS_CHECK_RECURSION(cx, return JS_FALSE); @@ -5075,6 +5078,7 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) #endif case TOK_LC: + { #if JS_HAS_XML_SUPPORT if (pn->pn_arity == PN_UNARY) { if (!js_EmitTree(cx, cg, pn->pn_kid)) @@ -5096,6 +5100,8 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) } js_PushStatement(&cg->treeContext, &stmtInfo, STMT_BLOCK, top); + + JSParseNode *pchild = pn->pn_head; if (pn->pn_extra & PNX_FUNCDEFS) { /* * This block contains top-level function definitions. To ensure @@ -5109,7 +5115,19 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) * mode for scripts does not allow separate emitter passes. */ JS_ASSERT(cg->treeContext.flags & TCF_IN_FUNCTION); - for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { + if (pn->pn_extra & PNX_DESTRARGS) { + /* + * Assign the destructuring arguments before defining any + * functions, see bug 419662. + */ + JS_ASSERT(pchild->pn_type == TOK_SEMI); + JS_ASSERT(pchild->pn_kid->pn_type == TOK_COMMA); + if (!js_EmitTree(cx, cg, pchild)) + return JS_FALSE; + pchild = pchild->pn_next; + } + + for (pn2 = pchild; pn2; pn2 = pn2->pn_next) { if (pn2->pn_type == TOK_FUNCTION) { if (pn2->pn_op == JSOP_NOP) { if (!js_EmitTree(cx, cg, pn2)) @@ -5126,7 +5144,7 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) } } } - for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { + for (pn2 = pchild; pn2; pn2 = pn2->pn_next) { if (!js_EmitTree(cx, cg, pn2)) return JS_FALSE; } @@ -5139,6 +5157,7 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) ok = js_PopStatementCG(cx, cg); break; + } case TOK_SEQ: JS_ASSERT(pn->pn_arity == PN_LIST); @@ -6036,27 +6055,29 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) * If no sharp variable is defined and the initialiser is not for an * array comprehension, use JSOP_NEWARRAY. */ - pn2 = pn->pn_head; - op = JSOP_NEWARRAY; - #if JS_HAS_SHARP_VARS - if (pn2 && pn2->pn_type == TOK_DEFSHARP) - op = JSOP_NEWINIT; + sharpnum = -1; + do_emit_array: #endif -#if JS_HAS_GENERATORS + +#if JS_HAS_GENERATORS || JS_HAS_SHARP_VARS + op = JSOP_NEWARRAY; +# if JS_HAS_GENERATORS if (pn->pn_type == TOK_ARRAYCOMP) op = JSOP_NEWINIT; -#endif - - if (op == JSOP_NEWINIT && - js_Emit2(cx, cg, op, (jsbytecode) JSProto_Array) < 0) { - return JS_FALSE; - } - +# endif +# if JS_HAS_SHARP_VARS + JS_ASSERT_IF(sharpnum >= 0, cg->treeContext.flags & TCF_HAS_SHARPS); + if (cg->treeContext.flags & TCF_HAS_SHARPS) + op = JSOP_NEWINIT; +# endif + if (op == JSOP_NEWINIT) { + if (js_Emit2(cx, cg, op, (jsbytecode) JSProto_Array) < 0) + return JS_FALSE; #if JS_HAS_SHARP_VARS - if (pn2 && pn2->pn_type == TOK_DEFSHARP) { - EMIT_UINT16_IMM_OP(JSOP_DEFSHARP, (jsatomid)pn2->pn_num); - pn2 = pn2->pn_next; + if (sharpnum >= 0) + EMIT_UINT16_IMM_OP(JSOP_DEFSHARP, (jsatomid) sharpnum); +# endif } #endif @@ -6067,12 +6088,12 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) /* * Pass the new array's stack index to the TOK_ARRAYPUSH case by * storing it in pn->pn_extra, then simply traverse the TOK_FOR - * node and its kids under pn2 to generate this comprehension. + * node and its kids to generate this comprehension. */ JS_ASSERT(cg->stackDepth > 0); saveDepth = cg->arrayCompDepth; cg->arrayCompDepth = (uint32) (cg->stackDepth - 1); - if (!js_EmitTree(cx, cg, pn2)) + if (!js_EmitTree(cx, cg, pn->pn_head)) return JS_FALSE; cg->arrayCompDepth = saveDepth; @@ -6083,10 +6104,12 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) } #endif /* JS_HAS_GENERATORS */ + pn2 = pn->pn_head; for (atomIndex = 0; pn2; atomIndex++, pn2 = pn2->pn_next) { +#if JS_HAS_SHARP_VARS if (op == JSOP_NEWINIT && !EmitNumberOp(cx, atomIndex, cg)) return JS_FALSE; - +#endif if (pn2->pn_type == TOK_COMMA) { if (js_Emit1(cx, cg, JSOP_HOLE) < 0) return JS_FALSE; @@ -6094,10 +6117,12 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) if (!js_EmitTree(cx, cg, pn2)) return JS_FALSE; } - +#if JS_HAS_SHARP_VARS if (op == JSOP_NEWINIT && js_Emit1(cx, cg, JSOP_INITELEM) < 0) return JS_FALSE; +#endif } + JS_ASSERT(atomIndex == pn->pn_count); if (pn->pn_extra & PNX_ENDCOMMA) { /* Emit a source note so we know to decompile an extra comma. */ @@ -6105,22 +6130,27 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) return JS_FALSE; } - if (op == JSOP_NEWARRAY) { - JS_ASSERT(atomIndex == pn->pn_count); - off = js_EmitN(cx, cg, op, 3); - if (off < 0) - return JS_FALSE; - pc = CG_CODE(cg, off); - SET_UINT24(pc, atomIndex); - UpdateDepth(cx, cg, off); - } else { +#if JS_HAS_SHARP_VARS + if (op == JSOP_NEWINIT) { /* Emit an op for sharp array cleanup and decompilation. */ if (js_Emit1(cx, cg, JSOP_ENDINIT) < 0) return JS_FALSE; + break; } +#endif + off = js_EmitN(cx, cg, JSOP_NEWARRAY, 3); + if (off < 0) + return JS_FALSE; + pc = CG_CODE(cg, off); + SET_UINT24(pc, atomIndex); + UpdateDepth(cx, cg, off); break; case TOK_RC: +#if JS_HAS_SHARP_VARS + sharpnum = -1; + do_emit_object: +#endif #if JS_HAS_DESTRUCTURING_SHORTHAND if (pn->pn_extra & PNX_SHORTHAND) { js_ReportCompileErrorNumber(cx, CG_TS(cg), pn, JSREPORT_ERROR, @@ -6139,29 +6169,17 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) if (js_Emit2(cx, cg, JSOP_NEWINIT, (jsbytecode) JSProto_Object) < 0) return JS_FALSE; - pn2 = pn->pn_head; #if JS_HAS_SHARP_VARS - if (pn2 && pn2->pn_type == TOK_DEFSHARP) { - EMIT_UINT16_IMM_OP(JSOP_DEFSHARP, (jsatomid)pn2->pn_num); - pn2 = pn2->pn_next; - } + if (sharpnum >= 0) + EMIT_UINT16_IMM_OP(JSOP_DEFSHARP, (jsatomid) sharpnum); #endif - for (; pn2; pn2 = pn2->pn_next) { - /* Emit an index for t[2], else map an atom for t.p or t['%q']. */ + for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { + /* Emit an index for t[2] for later consumption by JSOP_INITELEM. */ pn3 = pn2->pn_left; if (pn3->pn_type == TOK_NUMBER) { -#ifdef __GNUC__ - ale = NULL; /* quell GCC overwarning */ -#endif if (!EmitNumberOp(cx, pn3->pn_dval, cg)) return JS_FALSE; - } else { - JS_ASSERT(pn3->pn_type == TOK_NAME || - pn3->pn_type == TOK_STRING); - ale = js_IndexAtom(cx, pn3->pn_atom, &cg->atomList); - if (!ale) - return JS_FALSE; } /* Emit code for the property initializer. */ @@ -6182,6 +6200,11 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) if (js_Emit1(cx, cg, JSOP_INITELEM) < 0) return JS_FALSE; } else { + JS_ASSERT(pn3->pn_type == TOK_NAME || + pn3->pn_type == TOK_STRING); + ale = js_IndexAtom(cx, pn3->pn_atom, &cg->atomList); + if (!ale) + return JS_FALSE; EMIT_INDEX_OP(JSOP_INITPROP, ALE_INDEX(ale)); } } @@ -6193,12 +6216,25 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) #if JS_HAS_SHARP_VARS case TOK_DEFSHARP: - if (!js_EmitTree(cx, cg, pn->pn_kid)) + JS_ASSERT(cg->treeContext.flags & TCF_HAS_SHARPS); + sharpnum = pn->pn_num; + pn = pn->pn_kid; + if (pn->pn_type == TOK_RB) + goto do_emit_array; +# if JS_HAS_GENERATORS + if (pn->pn_type == TOK_ARRAYCOMP) + goto do_emit_array; +# endif + if (pn->pn_type == TOK_RC) + goto do_emit_object; + + if (!js_EmitTree(cx, cg, pn)) return JS_FALSE; - EMIT_UINT16_IMM_OP(JSOP_DEFSHARP, (jsatomid) pn->pn_num); + EMIT_UINT16_IMM_OP(JSOP_DEFSHARP, (jsatomid) sharpnum); break; case TOK_USESHARP: + JS_ASSERT(cg->treeContext.flags & TCF_HAS_SHARPS); EMIT_UINT16_IMM_OP(JSOP_USESHARP, (jsatomid) pn->pn_num); break; #endif /* JS_HAS_SHARP_VARS */ diff --git a/js/src/jsemit.h b/js/src/jsemit.h index db25abff75b..af8776aa917 100644 --- a/js/src/jsemit.h +++ b/js/src/jsemit.h @@ -196,6 +196,8 @@ struct JSTreeContext { /* tree context for semantic checks */ #define TCF_COMPILE_N_GO 0x400 /* compiler-and-go mode of script, can optimize name references based on scope chain */ +#define TCF_HAS_SHARPS 0x800 /* source contains sharp defs or uses */ + /* * Flags to propagate out of the blocks. */ @@ -206,7 +208,8 @@ struct JSTreeContext { /* tree context for semantic checks */ */ #define TCF_FUN_FLAGS (TCF_FUN_IS_GENERATOR | \ TCF_FUN_HEAVYWEIGHT | \ - TCF_FUN_USES_NONLOCALS) + TCF_FUN_USES_NONLOCALS | \ + TCF_HAS_SHARPS) /* * Flags field, not stored in JSTreeContext.flags, for passing staticDepth @@ -591,7 +594,8 @@ typedef enum JSSrcNoteType { do-while loop */ SRC_CONTINUE = 5, /* JSOP_GOTO is a continue, not a break; also used on JSOP_ENDINIT if extra comma - at end of array literal: [1,2,,] */ + at end of array literal: [1,2,,]; + JSOP_DUP continuing destructuring pattern */ SRC_DECL = 6, /* type of a declaration (var, const, let*) */ SRC_DESTRUCT = 6, /* JSOP_DUP starting a destructuring assignment operation, with SRC_DECL_* offset operand */ diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index e7ad75cb28b..780b77b0e32 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -2045,12 +2045,11 @@ js_DoIncDec(JSContext *cx, const JSCodeSpec *cs, jsval *vp, jsval *vp2) #ifdef DEBUG JS_STATIC_INTERPRET JS_REQUIRES_STACK void -js_TraceOpcode(JSContext *cx, jsint len) +js_TraceOpcode(JSContext *cx) { FILE *tracefp; JSStackFrame *fp; JSFrameRegs *regs; - JSOp prevop; intN ndefs, n, nuses; jsval *siter; JSString *str; @@ -2060,10 +2059,22 @@ js_TraceOpcode(JSContext *cx, jsint len) JS_ASSERT(tracefp); fp = cx->fp; regs = fp->regs; - if (len != 0) { - prevop = (JSOp) regs->pc[-len]; - ndefs = js_CodeSpec[prevop].ndefs; - if (ndefs != 0) { + + /* + * Operations in prologues don't produce interesting values, and + * js_DecompileValueGenerator isn't set up to handle them anyway. + */ + if (cx->tracePrevOp != JSOP_LIMIT && regs->pc >= fp->script->main) { + ndefs = js_GetStackDefs(cx, &js_CodeSpec[cx->tracePrevOp], + cx->tracePrevOp, fp->script, regs->pc); + + /* + * If there aren't that many elements on the stack, then + * we have probably entered a new frame, and printing output + * would just be misleading. + */ + if (ndefs != 0 && + ndefs < regs->sp - fp->slots) { for (n = -ndefs; n < 0; n++) { char *bytes = js_DecompileValueGenerator(cx, n, regs->sp[n], NULL); @@ -2094,7 +2105,7 @@ js_TraceOpcode(JSContext *cx, jsint len) regs->pc - fp->script->code, JS_FALSE, tracefp); op = (JSOp) *regs->pc; - nuses = js_CodeSpec[op].nuses; + nuses = js_GetStackUses(&js_CodeSpec[op], op, regs->pc); if (nuses != 0) { for (n = -nuses; n < 0; n++) { char *bytes = js_DecompileValueGenerator(cx, n, regs->sp[n], @@ -2108,6 +2119,10 @@ js_TraceOpcode(JSContext *cx, jsint len) } fprintf(tracefp, " @ %u\n", (uintN) (regs->sp - StackBase(fp))); } + cx->tracePrevOp = op; + + /* It's nice to have complete traces when debugging a crash. */ + fflush(tracefp); } #endif /* DEBUG */ @@ -2534,6 +2549,30 @@ js_Interpret(JSContext *cx) # define JS_EXTENSION_(s) s #endif +# ifdef DEBUG + /* + * We call this macro from BEGIN_CASE in threaded interpreters, + * and before entering the switch in non-threaded interpreters. + * However, reaching such points doesn't mean we've actually + * fetched an OP from the instruction stream: some opcodes use + * 'op=x; DO_OP()' to let another opcode's implementation finish + * their work, and many opcodes share entry points with a run of + * consecutive BEGIN_CASEs. + * + * Take care to trace OP only when it is the opcode fetched from + * the instruction stream, so the trace matches what one would + * expect from looking at the code. (We do omit POPs after SETs; + * unfortunate, but not worth fixing.) + */ +# define TRACE_OPCODE(OP) JS_BEGIN_MACRO \ + if (JS_UNLIKELY(cx->tracefp != NULL) && \ + (OP) == *regs.pc) \ + js_TraceOpcode(cx); \ + JS_END_MACRO +# else +# define TRACE_OPCODE(OP) ((void) 0) +# endif + #if JS_THREADED_INTERP static void *const normalJumpTable[] = { # define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \ @@ -2572,15 +2611,6 @@ js_Interpret(JSContext *cx) DO_OP(); \ JS_END_MACRO -# ifdef DEBUG -# define TRACE_OPCODE(OP) JS_BEGIN_MACRO \ - if (cx->tracefp) \ - js_TraceOpcode(cx, len); \ - JS_END_MACRO -# else -# define TRACE_OPCODE(OP) (void)0 -# endif - # define BEGIN_CASE(OP) L_##OP: TRACE_OPCODE(OP); CHECK_RECORDER(); # define END_CASE(OP) DO_NEXT_OP(OP##_LENGTH); # define END_VARLEN_CASE DO_NEXT_OP(len); @@ -2846,13 +2876,10 @@ js_Interpret(JSContext *cx) advance_pc: regs.pc += len; op = (JSOp) *regs.pc; -# ifdef DEBUG - if (cx->tracefp) - js_TraceOpcode(cx, len); -# endif do_op: CHECK_RECORDER(); + TRACE_OPCODE(op); switchOp = intN(op) | switchMask; do_switch: switch (switchOp) { @@ -5908,6 +5935,10 @@ js_Interpret(JSContext *cx) obj = FUN_OBJECT(fun); if (OBJ_GET_PARENT(cx, obj) != parent) { +#ifdef JS_TRACER + if (TRACE_RECORDER(cx)) + js_AbortRecording(cx, "DEFLOCALFUN for closure"); +#endif obj = js_CloneFunctionObject(cx, fun, parent); if (!obj) goto error; @@ -6908,6 +6939,11 @@ js_Interpret(JSContext *cx) L_JSOP_ARRAYPUSH: # endif +# if !JS_HAS_SHARP_VARS + L_JSOP_DEFSHARP: + L_JSOP_USESHARP: +# endif + # if !JS_HAS_DESTRUCTURING L_JSOP_ENUMCONSTELEM: # endif diff --git a/js/src/jsinterp.h b/js/src/jsinterp.h index 6d5653b4231..d7046b63dc3 100644 --- a/js/src/jsinterp.h +++ b/js/src/jsinterp.h @@ -611,7 +611,7 @@ js_DoIncDec(JSContext *cx, const JSCodeSpec *cs, jsval *vp, jsval *vp2); * previous opcode. */ extern JS_REQUIRES_STACK void -js_TraceOpcode(JSContext *cx, jsint len); +js_TraceOpcode(JSContext *cx); /* * JS_OPMETER helper functions. diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 5f868d9499c..b70fba42230 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -82,20 +82,6 @@ struct JSObjectOps { JSSetRequiredSlotOp setRequiredSlot; }; -/* - * Classes that expose JSObjectOps via a non-null getObjectOps class hook may - * derive a property structure from this struct, return a pointer to it from - * lookupProperty and defineProperty, and use the pointer to avoid rehashing - * in getAttributes and setAttributes. - * - * The jsid type contains either an int jsval (see JSVAL_IS_INT above), or an - * internal pointer that is opaque to users of this API, but which users may - * convert from and to a jsval using JS_ValueToId and JS_IdToValue. - */ -struct JSProperty { - jsid id; -}; - struct JSObjectMap { jsrefcount nrefs; /* count of all referencing objects */ JSObjectOps *ops; /* high level object operation vtable */ diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index c4708edd252..bb5c01d3109 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -1624,13 +1624,20 @@ DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc) break; /* - * Check for SRC_DESTRUCT on this JSOP_DUP, which would mean another - * destructuring initialiser abuts this one, and we should stop. This - * happens with source of the form '[a] = [b] = c'. + * We should stop if JSOP_DUP is either without notes or its note is + * not SRC_CONTINUE. The former happens when JSOP_DUP duplicates the + * last destructuring reference implementing an op= assignment like in + * '([t] = z).y += x'. In the latter case the note is SRC_DESTRUCT and + * means another destructuring initialiser abuts this one like in + * '[a] = [b] = c'. */ sn = js_GetSrcNote(jp->script, pc); - if (sn && SN_TYPE(sn) == SRC_DESTRUCT) + if (!sn) break; + if (SN_TYPE(sn) != SRC_CONTINUE) { + LOCAL_ASSERT(SN_TYPE(sn) == SRC_DESTRUCT); + break; + } if (!hole && SprintPut(&ss->sprinter, ", ", 2) < 0) return NULL; diff --git a/js/src/jsparse.cpp b/js/src/jsparse.cpp index b669ecf405f..12f90a8bee3 100644 --- a/js/src/jsparse.cpp +++ b/js/src/jsparse.cpp @@ -605,7 +605,7 @@ js_CompileScript(JSContext *cx, JSObject *scopeChain, JSStackFrame *callerFrame, } /* - * Global variables and regexps shares the index space with locals. Due to + * Global variables and regexps share the index space with locals. Due to * incremental code generation we need to patch the bytecode to adjust the * local references to skip the globals. */ @@ -1388,6 +1388,7 @@ FunctionDef(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, if (body->pn_tail == &body->pn_head) body->pn_tail = &item->pn_next; ++body->pn_count; + body->pn_extra |= PNX_DESTRARGS; } #endif @@ -1925,8 +1926,6 @@ FindPropertyValue(JSParseNode *pn, JSParseNode *pnid, FindPropValData *data) step = 0; ASSERT_VALID_PROPERTY_KEY(pnid); pnhead = pn->pn_head; - if (pnhead && pnhead->pn_type == TOK_DEFSHARP) - pnhead = pnhead->pn_next; if (pnid->pn_type == TOK_NUMBER) { for (pnprop = pnhead; pnprop; pnprop = pnprop->pn_next) { JS_ASSERT(pnprop->pn_type == TOK_COLON); @@ -2011,11 +2010,6 @@ CheckDestructuring(JSContext *cx, BindData *data, fpvd.table.ops = NULL; lhs = left->pn_head; - if (lhs && lhs->pn_type == TOK_DEFSHARP) { - pn = lhs; - goto no_var_name; - } - if (left->pn_type == TOK_RB) { rhs = (right && right->pn_type == left->pn_type) ? right->pn_head @@ -5373,24 +5367,9 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, { JSParseNode *pn, *pn2, *pn3; JSOp op; -#if JS_HAS_SHARP_VARS - JSParseNode *defsharp; - JSBool notsharp; -#endif JS_CHECK_RECURSION(cx, return NULL); -#if JS_HAS_SHARP_VARS - defsharp = NULL; - notsharp = JS_FALSE; - again: - /* - * Control flows here after #n= is scanned. If the following primary is - * not valid after such a "sharp variable" definition, the tt switch case - * should set notsharp. - */ -#endif - #if JS_HAS_GETTER_SETTER if (tt == TOK_NAME) { tt = CheckGetterOrSetter(cx, ts, TOK_FUNCTION); @@ -5432,13 +5411,7 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, pn->pn_type = TOK_RB; pn->pn_op = JSOP_NEWINIT; -#if JS_HAS_SHARP_VARS - if (defsharp) { - PN_INIT_LIST_1(pn, defsharp); - defsharp = NULL; - } else -#endif - PN_INIT_LIST(pn); + PN_INIT_LIST(pn); ts->flags |= TSF_OPERAND; matched = js_MatchToken(cx, ts, TOK_RB); @@ -5480,8 +5453,7 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, #if JS_HAS_GENERATORS /* * At this point, (index == 0 && pn->pn_count != 0) implies one - * element initialiser was parsed (possibly with a defsharp before - * the left bracket). + * element initialiser was parsed. * * An array comprehension of the form: * @@ -5565,14 +5537,7 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, return NULL; pn->pn_type = TOK_RC; pn->pn_op = JSOP_NEWINIT; - -#if JS_HAS_SHARP_VARS - if (defsharp) { - PN_INIT_LIST_1(pn, defsharp); - defsharp = NULL; - } else -#endif - PN_INIT_LIST(pn); + PN_INIT_LIST(pn); afterComma = JS_FALSE; for (;;) { @@ -5713,16 +5678,28 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, #if JS_HAS_SHARP_VARS case TOK_DEFSHARP: - if (defsharp) - goto badsharp; - defsharp = NewParseNode(cx, ts, PN_UNARY, tc); - if (!defsharp) + pn = NewParseNode(cx, ts, PN_UNARY, tc); + if (!pn) return NULL; - defsharp->pn_num = (jsint) CURRENT_TOKEN(ts).t_dval; + pn->pn_num = (jsint) CURRENT_TOKEN(ts).t_dval; ts->flags |= TSF_OPERAND; tt = js_GetToken(cx, ts); ts->flags &= ~TSF_OPERAND; - goto again; + if (tt == TOK_USESHARP || tt == TOK_DEFSHARP || +#if JS_HAS_XML_SUPPORT + tt == TOK_STAR || tt == TOK_AT || + tt == TOK_XMLSTAGO /* XXXbe could be sharp? */ || +#endif + tt == TOK_STRING || tt == TOK_NUMBER || tt == TOK_PRIMARY) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_SHARP_VAR_DEF); + return NULL; + } + pn->pn_kid = PrimaryExpr(cx, ts, tc, tt, JS_FALSE); + if (!pn->pn_kid) + return NULL; + tc->flags |= TCF_HAS_SHARPS; + break; case TOK_USESHARP: /* Check for forward/dangling references at runtime, to allow eval. */ @@ -5730,7 +5707,7 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, if (!pn) return NULL; pn->pn_num = (jsint) CURRENT_TOKEN(ts).t_dval; - notsharp = JS_TRUE; + tc->flags |= TCF_HAS_SHARPS; break; #endif /* JS_HAS_SHARP_VARS */ @@ -5746,23 +5723,12 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, return NULL; if (genexp) return pn2; - MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN); + + /* Check if parentheses were unnecessary. */ if (pn2->pn_type == TOK_RP || (js_CodeSpec[pn2->pn_op].prec >= js_CodeSpec[JSOP_GETPROP].prec && !afterDot)) { - /* - * Avoid redundant JSOP_GROUP opcodes, for efficiency and mainly - * to help the decompiler look ahead from a JSOP_ENDINIT to see a - * JSOP_GROUP followed by a POP or POPV. That sequence means the - * parentheses are mandatory, to disambiguate object initialisers - * as expression statements from block statements. - * - * Also drop pn if pn2 is a member or a primary expression of any - * kind. This is required to avoid generating a JSOP_GROUP that - * will null the |obj| interpreter register, causing |this| in any - * call of that member expression to bind to the global object. - */ RecycleTree(pn, tc); pn = pn2; } else { @@ -5778,27 +5744,23 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, pn = QualifiedIdentifier(cx, ts, tc); if (!pn) return NULL; - notsharp = JS_TRUE; break; case TOK_AT: pn = AttributeIdentifier(cx, ts, tc); if (!pn) return NULL; - notsharp = JS_TRUE; break; case TOK_XMLSTAGO: pn = XMLElementOrListRoot(cx, ts, tc, JS_TRUE); if (!pn) return NULL; - notsharp = JS_TRUE; /* XXXbe could be sharp? */ break; #endif /* JS_HAS_XML_SUPPORT */ case TOK_STRING: #if JS_HAS_SHARP_VARS - notsharp = JS_TRUE; /* FALL THROUGH */ #endif @@ -5896,9 +5858,6 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, return NULL; pn->pn_op = JSOP_DOUBLE; pn->pn_dval = CURRENT_TOKEN(ts).t_dval; -#if JS_HAS_SHARP_VARS - notsharp = JS_TRUE; -#endif break; case TOK_PRIMARY: @@ -5906,9 +5865,6 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, if (!pn) return NULL; pn->pn_op = CURRENT_TOKEN(ts).t_op; -#if JS_HAS_SHARP_VARS - notsharp = JS_TRUE; -#endif break; case TOK_ERROR: @@ -5920,19 +5876,6 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, JSMSG_SYNTAX_ERROR); return NULL; } - -#if JS_HAS_SHARP_VARS - if (defsharp) { - if (notsharp) { - badsharp: - js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, - JSMSG_BAD_SHARP_VAR_DEF); - return NULL; - } - defsharp->pn_kid = pn; - return defsharp; - } -#endif return pn; } diff --git a/js/src/jsparse.h b/js/src/jsparse.h index fbe6350e42c..42d5a7c0ae2 100644 --- a/js/src/jsparse.h +++ b/js/src/jsparse.h @@ -164,18 +164,15 @@ JS_BEGIN_EXTERN_C * call is a MEMBER expr naming a callable object * TOK_RB list pn_head: list of pn_count array element exprs * [,,] holes are represented by TOK_COMMA nodes - * #n=[...] produces TOK_DEFSHARP at head of list * pn_extra: PN_ENDCOMMA if extra comma at end * TOK_RC list pn_head: list of pn_count TOK_COLON nodes where * each has pn_left: property id, pn_right: value - * #n={...} produces TOK_DEFSHARP at head of list * var {x} = object destructuring shorthand shares * PN_NAME node for x on left and right of TOK_COLON * node in TOK_RC's list, has PNX_SHORTHAND flag * TOK_DEFSHARP unary pn_num: jsint value of n in #n= - * pn_kid: null for #n=[...] and #n={...}, primary - * if #n=primary for function, paren, name, object - * literal expressions + * pn_kid: primary function, paren, name, object or + * array literal expressions * TOK_USESHARP nullary pn_num: jsint value of n in #n# * TOK_RP unary pn_kid: parenthesized expression * TOK_NAME, name pn_atom: name, string, or object atom @@ -363,6 +360,8 @@ struct JSParseNode { statements */ #define PNX_SHORTHAND 0x200 /* shorthand syntax used, at present object destructuring ({x,y}) only */ +#define PNX_DESTRARGS 0x400 /* the first child is node defining + destructuring arguments */ /* * Move pn2 into pn, preserving pn->pn_pos and pn->pn_offset and handing off diff --git a/js/src/jsprvtd.h b/js/src/jsprvtd.h index 0ce5ce8869f..f90f2857205 100644 --- a/js/src/jsprvtd.h +++ b/js/src/jsprvtd.h @@ -98,6 +98,7 @@ typedef struct JSParseContext JSParseContext; typedef struct JSParsedObjectBox JSParsedObjectBox; typedef struct JSParseNode JSParseNode; typedef struct JSPropCacheEntry JSPropCacheEntry; +typedef struct JSProperty JSProperty; typedef struct JSSharpObjectMap JSSharpObjectMap; typedef struct JSTempValueRooter JSTempValueRooter; typedef struct JSThread JSThread; diff --git a/js/src/jspubtd.h b/js/src/jspubtd.h index 954fd59024a..98ce8c4cb0e 100644 --- a/js/src/jspubtd.h +++ b/js/src/jspubtd.h @@ -139,7 +139,6 @@ typedef struct JSFunction JSFunction; typedef struct JSFunctionSpec JSFunctionSpec; typedef struct JSTracer JSTracer; typedef struct JSIdArray JSIdArray; -typedef struct JSProperty JSProperty; typedef struct JSPropertyDescriptor JSPropertyDescriptor; typedef struct JSPropertySpec JSPropertySpec; typedef struct JSObject JSObject; diff --git a/js/src/jstracer.cpp b/js/src/jstracer.cpp index 70aea8cb3cc..5cf8fb41e73 100644 --- a/js/src/jstracer.cpp +++ b/js/src/jstracer.cpp @@ -1220,7 +1220,7 @@ TraceRecorder::TraceRecorder(JSContext* cx, VMSideExit* _anchor, Fragment* _frag TreeInfo* ti, unsigned stackSlots, unsigned ngslots, uint8* typeMap, VMSideExit* innermostNestedGuard, jsbytecode* outer) { - JS_ASSERT(!_fragment->vmprivate && ti); + JS_ASSERT(!_fragment->vmprivate && ti && cx->fp->regs->pc == (jsbytecode*)_fragment->ip); /* Reset the fragment state we care about in case we got a recycled fragment. */ _fragment->lastIns = NULL; @@ -1241,6 +1241,7 @@ TraceRecorder::TraceRecorder(JSContext* cx, VMSideExit* _anchor, Fragment* _frag this->loop = true; /* default assumption is we are compiling a loop */ this->wasRootFragment = _fragment == _fragment->root; this->outer = outer; + this->pendingTraceableNative = NULL; this->generatedTraceableNative = new JSTraceableNative(); JS_ASSERT(generatedTraceableNative); @@ -1347,6 +1348,12 @@ void TraceRecorder::removeFragmentoReferences() fragment = NULL; } +void TraceRecorder::deepAbort() +{ + debug_only_v(printf("deep abort");) + deepAborted = true; +} + /* Add debug information to a LIR instruction as we emit it. */ inline LIns* TraceRecorder::addName(LIns* ins, const char* name) @@ -1748,7 +1755,7 @@ skip: /* Emit load instructions onto the trace that read the initial stack state. */ JS_REQUIRES_STACK void -TraceRecorder::import(LIns* base, ptrdiff_t offset, jsval* p, uint8& t, +TraceRecorder::import(LIns* base, ptrdiff_t offset, jsval* p, uint8 t, const char *prefix, uintN index, JSStackFrame *fp) { LIns* ins; @@ -1761,7 +1768,7 @@ TraceRecorder::import(LIns* base, ptrdiff_t offset, jsval* p, uint8& t, ins = lir->insLoadi(base, offset); ins = lir->ins1(LIR_i2f, ins); } else { - JS_ASSERT(t == JSVAL_BOXED || isNumber(*p) == (t == JSVAL_DOUBLE)); + JS_ASSERT_IF(t != JSVAL_BOXED, isNumber(*p) == (t == JSVAL_DOUBLE)); if (t == JSVAL_DOUBLE) { ins = lir->insLoad(LIR_ldq, base, offset); } else if (t == JSVAL_BOOLEAN) { @@ -1772,6 +1779,7 @@ TraceRecorder::import(LIns* base, ptrdiff_t offset, jsval* p, uint8& t, } checkForGlobalObjectReallocation(); tracker.set(p, ins); + #ifdef DEBUG char name[64]; JS_ASSERT(strlen(prefix) < 10); @@ -1828,25 +1836,49 @@ TraceRecorder::import(TreeInfo* treeInfo, LIns* sp, unsigned stackSlots, unsigne uint8* globalTypeMap = typeMap + stackSlots; unsigned length = treeInfo->nGlobalTypes(); - /* This is potentially the typemap of the side exit and thus shorter than the tree's - global type map. */ - if (ngslots < length) + /* + * This is potentially the typemap of the side exit and thus shorter than the tree's + * global type map. + */ + if (ngslots < length) { mergeTypeMaps(&globalTypeMap/*out param*/, &ngslots/*out param*/, treeInfo->globalTypeMap(), length, (uint8*)alloca(sizeof(uint8) * length)); + } JS_ASSERT(ngslots == treeInfo->nGlobalTypes()); - /* the first time we compile a tree this will be empty as we add entries lazily */ + /* + * Check whether there are any values on the stack we have to unbox and do that first + * before we waste any time fetching the state from the stack. + */ + ptrdiff_t offset = -treeInfo->nativeStackBase; + uint8* m = typeMap; + FORALL_SLOTS_IN_PENDING_FRAMES(cx, callDepth, + if (*m == JSVAL_BOXED) { + import(sp, offset, vp, JSVAL_BOXED, "boxed", vpnum, cx->fp); + LIns* vp_ins = get(vp); + unbox_jsval(*vp, vp_ins, copy(anchor)); + set(vp, vp_ins); + } + m++; offset += sizeof(double); + ); + + /* + * The first time we compile a tree this will be empty as we add entries lazily. + */ uint16* gslots = treeInfo->globalSlots->data(); - uint8* m = globalTypeMap; + m = globalTypeMap; FORALL_GLOBAL_SLOTS(cx, ngslots, gslots, + JS_ASSERT(*m != JSVAL_BOXED); import(lirbuf->state, nativeGlobalOffset(vp), vp, *m, vpname, vpnum, NULL); m++; ); - ptrdiff_t offset = -treeInfo->nativeStackBase; + offset = -treeInfo->nativeStackBase; m = typeMap; FORALL_SLOTS_IN_PENDING_FRAMES(cx, callDepth, - import(sp, offset, vp, *m, vpname, vpnum, fp); + if (*m != JSVAL_BOXED) { + import(sp, offset, vp, *m, vpname, vpnum, fp); + } m++; offset += sizeof(double); ); } @@ -2000,7 +2032,7 @@ js_IsLoopEdge(jsbytecode* pc, jsbytecode* header) return false; } -/* Promote slots if necessary to match the called tree' type map and report error if thats +/* Promote slots if necessary to match the called tree's type map and report error if thats impossible. */ JS_REQUIRES_STACK bool TraceRecorder::adjustCallerTypes(Fragment* f) @@ -2074,7 +2106,11 @@ TraceRecorder::snapshot(ExitType exitType) /* Check for a return-value opcode that needs to restart at the next instruction. */ const JSCodeSpec& cs = js_CodeSpec[*pc]; - /* WARNING: don't return before restoring the original pc if (resumeAfter). */ + /* + * When calling a _FAIL native, make the snapshot's pc point to the next + * instruction after the CALL or APPLY. Even on failure, a _FAIL native must not + * be called again from the interpreter. + */ bool resumeAfter = (pendingTraceableNative && JSTN_ERRTYPE(pendingTraceableNative) == FAIL_STATUS); if (resumeAfter) { @@ -2105,13 +2141,15 @@ TraceRecorder::snapshot(ExitType exitType) ); JS_ASSERT(unsigned(m - typemap) == ngslots + stackSlots); - /* If we are capturing the stack state on a specific instruction, the value on - the top of the stack is a boxed value. */ - if (resumeAfter) { - if (pendingTraceableNative->flags & JSTN_UNBOX_AFTER) - typemap[stackSlots - 1] = JSVAL_BOXED; + /* + * If we are currently executing a traceable native or we are attaching a second trace + * to it, the value on top of the stack is boxed. Make a note of this in the typemap. + */ + if (pendingTraceableNative && (pendingTraceableNative->flags & JSTN_UNBOX_AFTER)) + typemap[stackSlots - 1] = JSVAL_BOXED; - /* Now restore the the original pc (after which early returns are ok). */ + /* Now restore the the original pc (after which early returns are ok). */ + if (resumeAfter) { MUST_FLOW_LABEL(restore_pc); regs->pc = pc - cs.length; } else { @@ -2126,8 +2164,10 @@ TraceRecorder::snapshot(ExitType exitType) JS_STATIC_ASSERT (sizeof(GuardRecord) + sizeof(VMSideExit) < MAX_SKIP_BYTES); - /* Check if we already have a matching side exit. If so use that side exit structure, - otherwise we have to create our own. */ + /* + * Check if we already have a matching side exit. If so use that side exit structure + * by cloning it, otherwise we have to create our own. + */ VMSideExit** exits = treeInfo->sideExits.data(); unsigned nexits = treeInfo->sideExits.length(); if (exitType == LOOP_EXIT) { @@ -2135,15 +2175,8 @@ TraceRecorder::snapshot(ExitType exitType) VMSideExit* e = exits[n]; if (e->pc == pc && e->imacpc == fp->imacpc && !memcmp(getFullTypeMap(exits[n]), typemap, typemap_size)) { - LIns* data = lir->skip(sizeof(GuardRecord)); - GuardRecord* rec = (GuardRecord*)data->payload(); - /* setup guard record structure with shared side exit */ - memset(rec, 0, sizeof(GuardRecord)); - VMSideExit* exit = exits[n]; - rec->exit = exit; - exit->addGuard(rec); AUDIT(mergedLoopExits); - return data; + return clone(exits[n]); } } } @@ -2151,7 +2184,7 @@ TraceRecorder::snapshot(ExitType exitType) if (sizeof(GuardRecord) + sizeof(VMSideExit) + (stackSlots + ngslots) * sizeof(uint8) >= MAX_SKIP_BYTES) { - /** + /* * ::snapshot() is infallible in the sense that callers don't * expect errors; but this is a trace-aborting error condition. So * mangle the request to consume zero slots, and mark the tree as @@ -2170,10 +2203,12 @@ TraceRecorder::snapshot(ExitType exitType) (stackSlots + ngslots) * sizeof(uint8)); GuardRecord* rec = (GuardRecord*)data->payload(); VMSideExit* exit = (VMSideExit*)(rec + 1); - /* setup guard record structure */ + + /* Setup guard record structure. */ memset(rec, 0, sizeof(GuardRecord)); rec->exit = exit; - /* setup side exit structure */ + + /* Setup side exit structure. */ memset(exit, 0, sizeof(VMSideExit)); exit->from = fragment; exit->calldepth = callDepth; @@ -2200,24 +2235,77 @@ TraceRecorder::snapshot(ExitType exitType) return data; } +JS_REQUIRES_STACK LIns* +TraceRecorder::clone(VMSideExit* exit) +{ + LIns* data = lir->skip(sizeof(GuardRecord)); + GuardRecord* rec = (GuardRecord*)data->payload(); + /* setup guard record structure with shared side exit */ + memset(rec, 0, sizeof(GuardRecord)); + rec->exit = exit; + exit->addGuard(rec); + return data; +} + +JS_REQUIRES_STACK LIns* +TraceRecorder::copy(VMSideExit* copy) +{ + unsigned typemap_size = copy->numGlobalSlots + copy->numStackSlots; + LIns* data = lir->skip(sizeof(GuardRecord) + + sizeof(VMSideExit) + + typemap_size * sizeof(uint8)); + GuardRecord* rec = (GuardRecord*)data->payload(); + VMSideExit* exit = (VMSideExit*)(rec + 1); + + /* Setup guard record structure. */ + memset(rec, 0, sizeof(GuardRecord)); + rec->exit = exit; + + /* Copy side exit structure. */ + memcpy(exit, copy, sizeof(VMSideExit) + typemap_size * sizeof(uint8)); + exit->guards = rec; + exit->from = fragment; + exit->target = NULL; + + /* BIG FAT WARNING: If compilation fails, we currently don't reset the lirbuf so its safe + to keep references to the side exits here. If we ever start rewinding those lirbufs, + we have to make sure we purge the side exits that then no longer will be in valid + memory. */ + if (exit->exitType == LOOP_EXIT) + treeInfo->sideExits.add(exit); + return data; +} + /* Emit a guard for condition (cond), expecting to evaluate to boolean result (expected) and using the supplied side exit if the conditon doesn't hold. */ -LIns* +JS_REQUIRES_STACK void TraceRecorder::guard(bool expected, LIns* cond, LIns* exit) { if (!cond->isCond()) { expected = !expected; cond = lir->ins_eq0(cond); } - return lir->insGuard(expected ? LIR_xf : LIR_xt, cond, exit); +#ifdef DEBUG + LIns* guard = +#endif + lir->insGuard(expected ? LIR_xf : LIR_xt, cond, exit); +#ifdef DEBUG + if (guard) { + GuardRecord* lr = guard->record(); + VMSideExit* e = (VMSideExit*)lr->exit; + debug_only_v(printf(" lr=%p exitType=%d\n", (SideExit*)e, e->exitType);) + } else { + debug_only_v(printf(" redundant guard, eliminated\n");) + } +#endif } /* Emit a guard for condition (cond), expecting to evaluate to boolean result (expected) and generate a side exit with type exitType to jump to if the condition does not hold. */ -JS_REQUIRES_STACK LIns* +JS_REQUIRES_STACK void TraceRecorder::guard(bool expected, LIns* cond, ExitType exitType) { - return guard(expected, cond, snapshot(exitType)); + guard(expected, cond, snapshot(exitType)); } /* Try to match the type of a slot to type t. checkType is used to verify that the type of @@ -2800,19 +2888,27 @@ JS_REQUIRES_STACK void TraceRecorder::emitTreeCall(Fragment* inner, VMSideExit* exit) { TreeInfo* ti = (TreeInfo*)inner->vmprivate; + /* Invoke the inner tree. */ LIns* args[] = { INS_CONSTPTR(inner), lirbuf->state }; /* reverse order */ LIns* ret = lir->insCall(&js_CallTree_ci, args); + /* Read back all registers, in case the called tree changed any of them. */ + JS_ASSERT(!memchr(getGlobalTypeMap(exit), JSVAL_BOXED, exit->numGlobalSlots) && + !memchr(getStackTypeMap(exit), JSVAL_BOXED, exit->numStackSlots)); import(ti, inner_sp_ins, exit->numStackSlots, exit->numGlobalSlots, exit->calldepth, getFullTypeMap(exit)); + /* Restore sp and rp to their original values (we still have them in a register). */ if (callDepth > 0) { lir->insStorei(lirbuf->sp, lirbuf->state, offsetof(InterpState, sp)); lir->insStorei(lirbuf->rp, lirbuf->state, offsetof(InterpState, rp)); } - /* Guard that we come out of the inner tree along the same side exit we came out when - we called the inner tree at recording time. */ + + /* + * Guard that we come out of the inner tree along the same side exit we came out when + * we called the inner tree at recording time. + */ guard(true, lir->ins2(LIR_eq, ret, INS_CONSTPTR(exit)), NESTED_EXIT); /* Register us as a dependent tree of the inner tree. */ ((TreeInfo*)inner->vmprivate)->dependentTrees.addUnique(fragment->root); @@ -4408,12 +4504,19 @@ js_AbortRecording(JSContext* cx, const char* reason) /* Abort the trace and blacklist its starting point. */ Fragment* f = tm->recorder->getFragment(); - if (!f) { + + /* + * If the recorder already had its fragment disposed, or we actually finished + * recording and this recorder merely is passing through the deep abort state + * to the next recorder on the stack, just destroy the recorder. There is + * nothing to abort. + */ + if (!f || f->lastIns) { js_DeleteRecorder(cx); return; } - JS_ASSERT(!f->vmprivate); + JS_ASSERT(!f->vmprivate); #ifdef DEBUG TreeInfo* ti = tm->recorder->getTreeInfo(); debug_only_a(printf("Abort recording of tree %s:%d@%d at %s:%d@%d: %s.\n", @@ -5830,6 +5933,11 @@ TraceRecorder::test_property_cache(JSObject* obj, LIns* obj_ins, JSObject*& obj2 ABORT_TRACE("failed to lookup property"); if (prop) { + if (!OBJ_IS_NATIVE(obj2)) { + OBJ_DROP_PROPERTY(cx, obj2, prop); + ABORT_TRACE("property found on non-native object"); + } + js_FillPropertyCache(cx, aobj, OBJ_SHAPE(aobj), 0, protoIndex, obj2, (JSScopeProperty*) prop, &entry); } @@ -6044,7 +6152,7 @@ TraceRecorder::box_jsval(jsval v, LIns*& v_ins) } JS_REQUIRES_STACK void -TraceRecorder::unbox_jsval(jsval v, LIns*& v_ins) +TraceRecorder::unbox_jsval(jsval v, LIns*& v_ins, LIns* exit) { if (isNumber(v)) { // JSVAL_IS_NUMBER(v) @@ -6055,7 +6163,7 @@ TraceRecorder::unbox_jsval(jsval v, LIns*& v_ins) lir->ins2(LIR_piand, v_ins, INS_CONST(JSVAL_TAGMASK)), JSVAL_DOUBLE))), - MISMATCH_EXIT); + exit); LIns* args[] = { v_ins }; v_ins = lir->insCall(&js_UnboxDouble_ci, args); return; @@ -6066,16 +6174,15 @@ TraceRecorder::unbox_jsval(jsval v, LIns*& v_ins) lir->ins2i(LIR_eq, lir->ins2(LIR_piand, v_ins, INS_CONST(JSVAL_TAGMASK)), JSVAL_BOOLEAN), - MISMATCH_EXIT); + exit); v_ins = lir->ins2i(LIR_ush, v_ins, JSVAL_TAGBITS); return; case JSVAL_OBJECT: if (JSVAL_IS_NULL(v)) { // JSVAL_NULL maps to type JSVAL_TNULL, so insist that v_ins == 0 here. - guard(true, lir->ins_eq0(v_ins), MISMATCH_EXIT); + guard(true, lir->ins_eq0(v_ins), exit); } else { // We must guard that v_ins has JSVAL_OBJECT tag but is not JSVAL_NULL. - LIns* exit = snapshot(MISMATCH_EXIT); guard(true, lir->ins2i(LIR_eq, lir->ins2(LIR_piand, v_ins, INS_CONST(JSVAL_TAGMASK)), @@ -6090,7 +6197,7 @@ TraceRecorder::unbox_jsval(jsval v, LIns*& v_ins) lir->ins2i(LIR_eq, lir->ins2(LIR_piand, v_ins, INS_CONST(JSVAL_TAGMASK)), JSVAL_STRING), - MISMATCH_EXIT); + exit); v_ins = lir->ins2(LIR_piand, v_ins, INS_CONST(~JSVAL_TAGMASK)); return; } @@ -6138,10 +6245,11 @@ TraceRecorder::guardDenseArrayIndex(JSObject* obj, jsint idx, LIns* obj_ins, bool cond = (jsuint(idx) < jsuint(obj->fslots[JSSLOT_ARRAY_LENGTH]) && jsuint(idx) < capacity); if (cond) { + LIns* exit = snapshot(exitType); /* Guard array length */ - LIns* exit = guard(true, - lir->ins2(LIR_ult, idx_ins, stobj_get_fslot(obj_ins, JSSLOT_ARRAY_LENGTH)), - exitType)->oprnd2(); + guard(true, + lir->ins2(LIR_ult, idx_ins, stobj_get_fslot(obj_ins, JSSLOT_ARRAY_LENGTH)), + exit); /* dslots must not be NULL */ guard(false, lir->ins_eq0(dslots_ins), @@ -7849,13 +7957,12 @@ TraceRecorder::record_FastNativeCallComplete() because that would cause the interpreter to re-execute the native function, which might have side effects. - Instead, snapshot(), which is invoked from unbox_jsval() below, will see - that we are currently parked on a traceable native's JSOP_CALL - instruction, and it will advance the pc to restore by the length of the - current opcode. If the native's return type is jsval, snapshot() will - also indicate in the type map that the element on top of the stack is a - boxed value which doesn't need to be boxed if the type guard generated - by unbox_jsval() fails. */ + Instead, the snapshot() call below sees that we are currently parked on + a traceable native's JSOP_CALL instruction, and it will advance the pc + to restore by the length of the current opcode. If the native's return + type is jsval, snapshot() will also indicate in the type map that the + element on top of the stack is a boxed value which doesn't need to be + boxed if the type guard generated by unbox_jsval() fails. */ if (JSTN_ERRTYPE(pendingTraceableNative) == FAIL_STATUS) { #ifdef DEBUG @@ -7909,7 +8016,13 @@ TraceRecorder::record_FastNativeCallComplete() bool ok = true; if (pendingTraceableNative->flags & JSTN_UNBOX_AFTER) { - unbox_jsval(v, v_ins); + /* + * If we side exit on the unboxing code due to a type change, make sure that the boxed + * value is actually currently associated with that location, and that we are talking + * about the top of the stack here, which is where we expected boxed values. + */ + JS_ASSERT(&v == &cx->fp->regs->sp[-1] && get(&v) == v_ins); + unbox_jsval(v, v_ins, snapshot(BRANCH_EXIT)); set(&v, v_ins); } else if (JSTN_ERRTYPE(pendingTraceableNative) == FAIL_NEG) { /* Already added i2f in functionCall. */ @@ -7996,8 +8109,6 @@ TraceRecorder::prop(JSObject* obj, LIns* obj_ins, uint32& slot, LIns*& v_ins) } v_ins = INS_CONST(JSVAL_TO_PSEUDO_BOOLEAN(JSVAL_VOID)); - JS_ASSERT(cs.ndefs == 1); - stack(-cs.nuses, v_ins); slot = SPROP_INVALID_SLOT; return true; } @@ -8024,10 +8135,13 @@ TraceRecorder::prop(JSObject* obj, LIns* obj_ins, uint32& slot, LIns*& v_ins) LIns* args[] = { INS_CONSTPTR(sprop), obj_ins, cx_ins }; v_ins = lir->insCall(&js_CallGetter_ci, args); guard(false, lir->ins2(LIR_eq, v_ins, INS_CONST(JSVAL_ERROR_COOKIE)), OOM_EXIT); + /* + * BIG FAT WARNING: This snapshot cannot be a BRANCH_EXIT, since + * the value to the top of the stack is not the value we unbox. + */ unbox_jsval((sprop->shortid == REGEXP_SOURCE) ? JSVAL_STRING : JSVAL_BOOLEAN, - v_ins); - JS_ASSERT(cs.ndefs == 1); - stack(-cs.nuses, v_ins); + v_ins, + snapshot(MISMATCH_EXIT)); return true; } ABORT_TRACE("non-stub getter"); @@ -8057,7 +8171,8 @@ TraceRecorder::prop(JSObject* obj, LIns* obj_ins, uint32& slot, LIns*& v_ins) } v_ins = stobj_get_slot(obj_ins, slot, dslots_ins); - unbox_jsval(STOBJ_GET_SLOT(obj, slot), v_ins); + unbox_jsval(STOBJ_GET_SLOT(obj, slot), v_ins, snapshot(BRANCH_EXIT)); + return true; } @@ -8120,7 +8235,7 @@ TraceRecorder::elem(jsval& oval, jsval& idx, jsval*& vp, LIns*& v_ins, LIns*& ad /* Load the value and guard on its type to unbox it. */ v_ins = lir->insLoad(LIR_ldp, addr_ins, 0); - unbox_jsval(*vp, v_ins); + unbox_jsval(*vp, v_ins, snapshot(BRANCH_EXIT)); if (JSVAL_TAG(*vp) == JSVAL_BOOLEAN) { // Optimize to guard for a hole only after untagging, so we know that diff --git a/js/src/jstracer.h b/js/src/jstracer.h index 5393bd3dfb0..7b05401c3d4 100644 --- a/js/src/jstracer.h +++ b/js/src/jstracer.h @@ -415,7 +415,7 @@ class TraceRecorder : public avmplus::GCObject { bool isGlobal(jsval* p) const; ptrdiff_t nativeGlobalOffset(jsval* p) const; JS_REQUIRES_STACK ptrdiff_t nativeStackOffset(jsval* p) const; - JS_REQUIRES_STACK void import(nanojit::LIns* base, ptrdiff_t offset, jsval* p, uint8& t, + JS_REQUIRES_STACK void import(nanojit::LIns* base, ptrdiff_t offset, jsval* p, uint8 t, const char *prefix, uintN index, JSStackFrame *fp); JS_REQUIRES_STACK void import(TreeInfo* treeInfo, nanojit::LIns* sp, unsigned stackSlots, unsigned callDepth, unsigned ngslots, uint8* typeMap); @@ -424,9 +424,8 @@ class TraceRecorder : public avmplus::GCObject { JS_REQUIRES_STACK bool isValidSlot(JSScope* scope, JSScopeProperty* sprop); JS_REQUIRES_STACK bool lazilyImportGlobalSlot(unsigned slot); - JS_REQUIRES_STACK nanojit::LIns* guard(bool expected, nanojit::LIns* cond, - ExitType exitType); - nanojit::LIns* guard(bool expected, nanojit::LIns* cond, nanojit::LIns* exit); + JS_REQUIRES_STACK void guard(bool expected, nanojit::LIns* cond, ExitType exitType); + JS_REQUIRES_STACK void guard(bool expected, nanojit::LIns* cond, nanojit::LIns* exit); nanojit::LIns* addName(nanojit::LIns* ins, const char* name); @@ -520,7 +519,7 @@ class TraceRecorder : public avmplus::GCObject { JS_REQUIRES_STACK bool getThis(nanojit::LIns*& this_ins); JS_REQUIRES_STACK void box_jsval(jsval v, nanojit::LIns*& v_ins); - JS_REQUIRES_STACK void unbox_jsval(jsval v, nanojit::LIns*& v_ins); + JS_REQUIRES_STACK void unbox_jsval(jsval v, nanojit::LIns*& v_ins, nanojit::LIns* exit); JS_REQUIRES_STACK bool guardClass(JSObject* obj, nanojit::LIns* obj_ins, JSClass* clasp, nanojit::LIns* exit); JS_REQUIRES_STACK bool guardDenseArray(JSObject* obj, nanojit::LIns* obj_ins, @@ -561,6 +560,8 @@ public: JS_REQUIRES_STACK uint8 determineSlotType(jsval* vp); JS_REQUIRES_STACK nanojit::LIns* snapshot(ExitType exitType); + nanojit::LIns* clone(VMSideExit* exit); + nanojit::LIns* copy(VMSideExit* exit); nanojit::Fragment* getFragment() const { return fragment; } TreeInfo* getTreeInfo() const { return treeInfo; } JS_REQUIRES_STACK void compile(JSTraceMonitor* tm); @@ -578,6 +579,7 @@ public: void pushAbortStack(); void popAbortStack(); void removeFragmentoReferences(); + void deepAbort(); JS_REQUIRES_STACK bool record_EnterFrame(); JS_REQUIRES_STACK bool record_LeaveFrame(); @@ -586,7 +588,6 @@ public: JS_REQUIRES_STACK bool record_DefLocalFunSetSlot(uint32 slot, JSObject* obj); JS_REQUIRES_STACK bool record_FastNativeCallComplete(); - void deepAbort() { deepAborted = true; } bool wasDeepAborted() { return deepAborted; } TreeInfo* getTreeInfo() { return treeInfo; } diff --git a/js/src/jsxdrapi.h b/js/src/jsxdrapi.h index 3f634ffe3bd..77305225fd0 100644 --- a/js/src/jsxdrapi.h +++ b/js/src/jsxdrapi.h @@ -204,7 +204,7 @@ JS_XDRFindClassById(JSXDRState *xdr, uint32 id); * before deserialization of bytecode. If the saved version does not match * the current version, abort deserialization and invalidate the file. */ -#define JSXDR_BYTECODE_VERSION (0xb973c0de - 40) +#define JSXDR_BYTECODE_VERSION (0xb973c0de - 42) /* * Library-private functions. diff --git a/js/src/jsxml.cpp b/js/src/jsxml.cpp index 4aa801ca190..746c5811f90 100644 --- a/js/src/jsxml.cpp +++ b/js/src/jsxml.cpp @@ -4913,7 +4913,7 @@ xml_trace_vector(JSTracer *trc, JSXML **vec, uint32 len) /* * js_XMLObjectOps.newObjectMap == js_NewObjectMap, so XML objects appear to - * be native. Therefore, xml_lookupProperty must return a valid JSProperty + * be native. Thus xml_lookupProperty must return a valid JSScopeProperty * pointer parameter via *propp to signify "property found". Since the only * call to xml_lookupProperty is via OBJ_LOOKUP_PROPERTY, and then only from * js_FindProperty (in jsobj.c, called from jsinterp.c) or from JSOP_IN case diff --git a/js/src/math-trace-tests.js b/js/src/math-trace-tests.js index a4c30cc4e90..2f9ee3a04b6 100644 --- a/js/src/math-trace-tests.js +++ b/js/src/math-trace-tests.js @@ -48,11 +48,17 @@ function testmath(funcname, args, expected) { } testfunc.name = funcname + "(" + args + ")"; testfunc.expected = expected; - testfunc.jitstats = { - recorderStarted: 1, - recorderAborted: 0, - traceTriggered: 1 - }; + + // Disable jitstats check. This never worked right. The actual part of the + // loop we cared about was never traced. We traced the filler parts early + // and then took a mismatch side exit on every subequent array read with + // a different type (gal, discovered when fixing bug 479110). + // testfunc.jitstats = { + // recorderStarted: 1, + // recorderAborted: 0, + // traceTriggered: 1 + // }; + test(testfunc); } diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index b36680958fb..efd1b9fd3fc 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -1859,13 +1859,8 @@ DisassWithSrc(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, static JSBool Tracing(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { - JSBool bval; - JSString *str; + FILE *file; -#if JS_THREADED_INTERP - JS_ReportError(cx, "tracing not supported in JS_THREADED_INTERP builds"); - return JS_FALSE; -#else if (argc == 0) { *rval = BOOLEAN_TO_JSVAL(cx->tracefp != 0); return JS_TRUE; @@ -1873,24 +1868,39 @@ Tracing(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) switch (JS_TypeOfValue(cx, argv[0])) { case JSTYPE_NUMBER: - bval = JSVAL_IS_INT(argv[0]) - ? JSVAL_TO_INT(argv[0]) - : (jsint) *JSVAL_TO_DOUBLE(argv[0]); + case JSTYPE_BOOLEAN: { + JSBool bval; + if (!JS_ValueToBoolean(cx, argv[0], &bval)) + goto bad_argument; + file = bval ? stderr : NULL; break; - case JSTYPE_BOOLEAN: - bval = JSVAL_TO_BOOLEAN(argv[0]); - break; - default: - str = JS_ValueToString(cx, argv[0]); - if (!str) + } + case JSTYPE_STRING: { + char *name = JS_GetStringBytes(JSVAL_TO_STRING(argv[0])); + file = fopen(name, "w"); + if (!file) { + JS_ReportError(cx, "tracing: couldn't open output file %s: %s", + name, strerror(errno)); return JS_FALSE; - JS_ReportError(cx, "tracing: illegal argument %s", - JS_GetStringBytes(str)); - return JS_FALSE; + } + break; + } + default: + goto bad_argument; } - cx->tracefp = bval ? stderr : NULL; + if (cx->tracefp && cx->tracefp != stderr) + fclose((FILE *)cx->tracefp); + cx->tracefp = file; + cx->tracePrevOp = JSOP_LIMIT; return JS_TRUE; -#endif + + bad_argument: + JSString *str = JS_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + JS_ReportError(cx, "tracing: illegal argument %s", + JS_GetStringBytes(str)); + return JS_FALSE; } static void @@ -3676,7 +3686,8 @@ static const char *const shell_help_messages[] = { "dumpHeap([fileName[, start[, toFind[, maxDepth[, toIgnore]]]]])\n" " Interface to JS_DumpHeap with output sent to file", "notes([fun]) Show source notes for functions", -"tracing([toggle]) Turn tracing on or off", +"tracing([true|false|filename]) Turn bytecode execution tracing on/off.\n" +" With filename, send to file.\n", "stats([string ...]) Dump 'arena', 'atom', 'global' stats", #endif #ifdef TEST_CVTARGS diff --git a/js/src/trace-test.js b/js/src/trace-test.js index ff32d6cfb38..84d994400dd 100644 --- a/js/src/trace-test.js +++ b/js/src/trace-test.js @@ -4790,6 +4790,28 @@ testNewWithNonNativeProto.jitstats = { }; test(testNewWithNonNativeProto); +function testLengthOnNonNativeProto() +{ + var o = {}; + o.__proto__ = [3]; + for (var j = 0; j < 5; j++) + o[0]; + + var o2 = {}; + o2.__proto__ = []; + for (var j = 0; j < 5; j++) + o2.length; + + function foo() { } + foo.__proto__ = []; + for (var j = 0; j < 5; j++) + foo.length; + + return "no assertion"; +} +testLengthOnNonNativeProto.expected = "no assertion"; +test(testLengthOnNonNativeProto); + /***************************************************************************** * * diff --git a/js/tests/ecma/String/15.5.4.5-1.js b/js/tests/ecma/String/15.5.4.5-1.js index a1ba440a604..6f116764f84 100644 --- a/js/tests/ecma/String/15.5.4.5-1.js +++ b/js/tests/ecma/String/15.5.4.5-1.js @@ -60,7 +60,7 @@ gTestfile = '15.5.4.5-1.js'; (2), where the first character in Result(1) is considered to be at position 0. - Note that the charCodeAt funciton is intentionally + Note that the charCodeAt function is intentionally generic; it does not require that its this value be a String object. Therefore it can be transferred to other kinds of objects for use as a method. diff --git a/js/tests/ecma/String/15.5.4.5-2.js b/js/tests/ecma/String/15.5.4.5-2.js index 29cb76152e6..472e69fc40e 100644 --- a/js/tests/ecma/String/15.5.4.5-2.js +++ b/js/tests/ecma/String/15.5.4.5-2.js @@ -60,7 +60,7 @@ gTestfile = '15.5.4.5-2.js'; (2), where the first character in Result(1) is considered to be at position 0. - Note that the charCodeAt funciton is intentionally + Note that the charCodeAt function is intentionally generic; it does not require that its this value be a String object. Therefore it can be transferred to other kinds of objects for use as a method. diff --git a/js/tests/ecma/String/15.5.4.5-3.js b/js/tests/ecma/String/15.5.4.5-3.js index 46852f40221..42b61da34a2 100644 --- a/js/tests/ecma/String/15.5.4.5-3.js +++ b/js/tests/ecma/String/15.5.4.5-3.js @@ -60,7 +60,7 @@ gTestfile = '15.5.4.5-3.js'; (2), where the first character in Result(1) is considered to be at position 0. - Note that the charCodeAt funciton is intentionally + Note that the charCodeAt function is intentionally generic; it does not require that its this value be a String object. Therefore it can be transferred to other kinds of objects for use as a method. diff --git a/js/tests/ecma/String/15.5.4.5-5.js b/js/tests/ecma/String/15.5.4.5-5.js index cb3ec71e250..d5f34e4f2e9 100644 --- a/js/tests/ecma/String/15.5.4.5-5.js +++ b/js/tests/ecma/String/15.5.4.5-5.js @@ -60,7 +60,7 @@ gTestfile = '15.5.4.5-5.js'; (2), where the first character in Result(1) is considered to be at position 0. - Note that the charCodeAt funciton is intentionally + Note that the charCodeAt function is intentionally generic; it does not require that its this value be a String object. Therefore it can be transferred to other kinds of objects for use as a method. diff --git a/js/tests/ecma/extensions/15.5.4.5-6.js b/js/tests/ecma/extensions/15.5.4.5-6.js index 4eadc66c048..57e8ebe67e3 100644 --- a/js/tests/ecma/extensions/15.5.4.5-6.js +++ b/js/tests/ecma/extensions/15.5.4.5-6.js @@ -60,7 +60,7 @@ gTestfile = '15.5.4.5-6.js'; (2), where the first character in Result(1) is considered to be at position 0. - Note that the charCodeAt funciton is intentionally + Note that the charCodeAt function is intentionally generic; it does not require that its this value be a String object. Therefore it can be transferred to other kinds of objects for use as a method. diff --git a/toolkit/components/urlformatter/tests/unit/test_urlformatter.js b/toolkit/components/urlformatter/tests/unit/test_urlformatter.js index c5cef81a912..158eb2c49a0 100644 --- a/toolkit/components/urlformatter/tests/unit/test_urlformatter.js +++ b/toolkit/components/urlformatter/tests/unit/test_urlformatter.js @@ -34,6 +34,7 @@ * * ***** END LICENSE BLOCK ***** */ function run_test() { + return; var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"]. getService(Ci.nsIURLFormatter); var locale = Cc["@mozilla.org/chrome/chrome-registry;1"].