Bug 799185 part 2 - Add comments to ReconstructPCStack. r=jorendorff
authorNicolas 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 id18520
push usernpierron@mozilla.com
push dateFri, 23 Nov 2012 00:53:22 +0000
treeherdermozilla-inbound@b3ec22e8f727 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs799185
milestone20.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
Bug 799185 part 2 - Add comments to ReconstructPCStack. r=jorendorff
js/src/jsopcode.cpp
--- 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;
             }
         }