author | Nicolas B. Pierron <nicolas.b.pierron@mozilla.com> |
Thu, 22 Nov 2012 16:09:14 -0800 | |
changeset 114052 | ffa9803fa86d1c6b28d1d781f3517282a213f4ac |
parent 114051 | daa8d72e3e9185461758d3ebe4713a40139b21b9 |
child 114053 | 6aabb17a3cffa0c641d27c87723412029a3d755f |
push id | 18520 |
push user | npierron@mozilla.com |
push date | Fri, 23 Nov 2012 00:53:22 +0000 |
treeherder | mozilla-inbound@b3ec22e8f727 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jorendorff |
bugs | 799185 |
milestone | 20.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -6544,60 +6544,213 @@ AssertPCDepth(JSScript *script, jsbyteco static int ReconstructPCStack(JSContext *cx, JSScript *script, jsbytecode *target, jsbytecode **pcstack) { /* * Walk forward from script->code and compute the stack depth and stack of * operand-generating opcode PCs in pcstack. * - * FIXME: Code to compute oplen copied from js_Disassemble1 and reduced. - * FIXME: Optimize to use last empty-stack sequence point. + * Every instruction has a statically computable stack depth before and + * after. This function returns the stack depth before the target + * instruction. + * + * The stack depth can be computed from these three common-sense rules... + * + * - The stack depth before the first instruction of the script is 0. + * + * - The stack depth after an instruction is always simply the stack + * depth before, plus whatever effect of the instruction has. + * SimulateOp computes this. + * + * - Except for three specific cases listed below, the stack depth before + * an instruction is simply the stack depth after the preceding one. + * + * ... and these three less obvious rules: + * + * - Special case 1: break, continue, or return statements that exit + * certain kinds of blocks. + * + * Rule: The stack depth before the next instruction (following the + * GOTO/RETRVAL) equals the stack depth before the first SRC_HIDDEN + * instruction. + * + * - Special case 2: The rethrow at the end of conditional catch blocks. + * + * Rule: The stack depth before the next rethrow instruction of a + * conditional catch block equals the stack depth at the end of the IFEQ + * of the catch condition (which is jumping to this re-throw). + * + * - Special case 3: Conditional expressions (A ? B : C). + * + * Rule: The code for B and C are pushing a result on the stack, so the + * stack depth before C equals the stack depth before B, which is one + * less than the stack depth after the GOTO ending B. */ LOCAL_ASSERT(script->code <= target && target < script->code + script->length); + + /* + * Save depth of hidden instructions to recover the depth if a branch is not + * taken. + */ unsigned hpcdepth = unsigned(-1); + + /* + * Save depth of catch instructions to recover it in the rethrow path, at + * the end of a conditional catch. + */ unsigned cpcdepth = unsigned(-1); jsbytecode *pc; unsigned pcdepth; ptrdiff_t oplen; for (pc = script->code, pcdepth = 0; ; pc += oplen) { JSOp op = JSOp(*pc); oplen = GetBytecodeLength(pc); const JSCodeSpec *cs = &js_CodeSpec[op]; jssrcnote *sn = js_GetSrcNote(cx, script, pc); + /* + * *** Special case 1.a *** + * + * Hidden instructions are used to pop scoped variables (leaveblock) in + * early-exit paths and are not supposed to affect the stack depth when + * they are not taken. However, when the target is in an early-exit + * path, hidden instructions need to be taken into account. + * + * As we are not following any branch, we need to restore the stack + * depth after any instruction which is breaking the flow of the + * program. Sequences of hidden instructions are *included* in the + * following grammar: + * + * ExitSequence: + * EarlyExit // return / break / continue + * | ConditionalCatchExit // conditional catch-block exit + * + * EarlyExit: + * setrval; ExitSegment* retrval; // return stmt + * | ExitSegment* goto; // break/continue stmt + * + * ConditionalCatchExit: + * ExitSegment* hidden goto; CatchRethrow + * + * CatchRethrow: + * hidden throw; end-brace nop; // last catch-block + * | hidden throw; finally; // followed by a finally + * | hidden throwing; hidden leaveblock; catch enterblock; + * // followed by a catch-block + * + * ExitSegment: + * hidden leaveblock; // leave let-block or + * // leave catch-block + * | hidden leavewith; // leave with-block + * | hidden gosub; // leave try or + * // leave catch-block with finally + * | hidden enditer; // leave for-in/of block + * | hidden leaveforletin; hidden enditer; hidden popn; + * // leave for-let-in/of block + * + * The following code save the depth of the first hidden + * instruction. When we hit either the last instruction of the EarlyExit + * (retrval/goto) or the goto of a ConditionalCacthExit, we restore (see + * Special case 1.b) the depth after the execution of the instruction. + * + * In case of a ConditionalCacthExit, we keep the hidden depth after the + * hidden goto to again restore (see Special case 1.b) the depth after + * the throw/throwing instruction of the CatchRethrow. + * + * Note: The last instructions of the EarlyExit, such as break, continue + * and return are not hidden as they are part of the original sources, + * so we need to consider them as-if they were hidden instruction (do + * not reset the depth) and restore the depth after their execution. It + * is important to restore them after their execution, and not at the + * next instruction, because they might be followed by another sequence + * of hidden instruction. + */ bool exitPath = op == JSOP_GOTO || op == JSOP_RETRVAL || op == JSOP_THROW; - bool isHiddenGoto = false; + bool isConditionalCatchExit = false; if (sn && SN_TYPE(sn) == SRC_HIDDEN) { - isHiddenGoto = op == JSOP_GOTO; + /* Only ConditionalCatchExit's goto are hidden. */ + isConditionalCatchExit = op == JSOP_GOTO; if (hpcdepth == unsigned(-1)) hpcdepth = pcdepth; } else if (!exitPath) { hpcdepth = unsigned(-1); } + + /* + * *** Special case 2 *** + * + * The catch depth is used to restore the depth expected in case of + * rethrow or in case of guard failure. The depth expected by throw and + * throwing instructions correspond to the result of enterblock and + * exception instructions. + * + * enterblock depth d <-- depth += d + * exception <-- depth += 1 + * + * This is not a problem, except with conditional catch staements. + * Conditional catch statements are looking like: + * + * ifeq +... <-- jump to the rethrow path. + * pop <-- pop the exception value. + * + * Unfortunately, we iterate linearly over the bytecode, so we cannot + * use a stack to save the depth for each catch statement that we + * encounter. The trick done here is to save the stack depth before we + * unwind the scoped variables with leaveblock, and restore the stack + * depth plus one to account for the missing exception value. + * + * {Catch} leaveblock d <-- save depth + * ( gosub ... )? + * {Hidden} goto ... + * ( {Hidden} throw )? <-- restore depth + 1 + * + * If there is no rethrow, then we should expect an end-brace nop or a + * finally. + */ if (op == JSOP_LEAVEBLOCK && sn && SN_TYPE(sn) == SRC_CATCH) { LOCAL_ASSERT(cpcdepth == unsigned(-1)); cpcdepth = pcdepth; } else if (sn && SN_TYPE(sn) == SRC_HIDDEN && (op == JSOP_THROW || op == JSOP_THROWING)) { + /* + * The current catch block is conditional, we compensate for the pop + * of the exception added after the ifeq by adding 1 to the restored + * depth. This fake slot might not contain the right value, but + * it will be eliminated with the throw or throwing instruction. + */ LOCAL_ASSERT(cpcdepth != unsigned(-1)); pcdepth = cpcdepth + 1; cpcdepth = unsigned(-1); } else if (!(op == JSOP_GOTO && sn && SN_TYPE(sn) == SRC_HIDDEN) && !(op == JSOP_GOSUB && cpcdepth != unsigned(-1))) { + /* + * We need to ignore gosub and goto when we exit a catch block. + * Otherwise we might reset the catch depth before hitting the throw + * or throwing instruction. + * + * {Catch} leaveblock d + * ( gosub ... )? + * {Hidden} goto ... + * ( {Hidden} throw )? + * finally + * + * If there is no rethrow, the end-brace nop or the finally + * instruction will reset the saved depth. + */ if (cpcdepth != unsigned(-1)) LOCAL_ASSERT((op == JSOP_NOP && sn && SN_TYPE(sn) == SRC_ENDBRACE) || op == JSOP_FINALLY); cpcdepth = unsigned(-1); } /* At this point, pcdepth is the stack depth *before* the insn at pc. */ AssertPCDepth(script, pc, pcdepth); @@ -6605,26 +6758,42 @@ ReconstructPCStack(JSContext *cx, JSScri break; /* Simulate the instruction at pc. */ if (SimulateOp(script, op, cs, pc, pcstack, pcdepth) < 0) return -1; /* At this point, pcdepth is the stack depth *after* the insn at pc. */ + /* + * *** Special case 1.b *** + * + * Restore the stack depth after the execution of an EarlyExit or the + * goto of the ConditionalCatchExit. + */ if (exitPath && hpcdepth != unsigned(-1)) { pcdepth = hpcdepth; - if (!isHiddenGoto) + + /* Keep the depth if we are on the ConditionalCatchExit path. */ + if (!isConditionalCatchExit) hpcdepth = unsigned(-1); } /* + * *** Special case 3 *** + * * A (C ? T : E) expression requires skipping T if target is in E or * after the whole expression, because this expression is pushing a * result on the stack and the goto cannot be skipped. + * + * 09 001 pc : ifeq +11 + * 000 pc+ 5: null + * 001 pc+ 6: goto +... <-- The stack after the goto + * 000 pc+11: ... <-- does not have the same depth. + * */ if (sn && SN_TYPE(sn) == SRC_COND) { ptrdiff_t jmplen = GET_JUMP_OFFSET(pc); if (pc + jmplen <= target) { /* Target does not lie in T. */ oplen = jmplen; } }