Given this object's code list, compute its maximal stack usage.
        This is done by scanning the code, and computing for each opcode
        the stack state at the opcode.

def _compute_stacksize(self):
        '''
        Given this object's code list, compute its maximal stack usage.
        This is done by scanning the code, and computing for each opcode
        the stack state at the opcode.

        '''

        # get local access to code, save some attribute lookups later
        code = self.code

        # A mapping from labels to their positions in the code list
        label_pos = { op : pos
                        for pos, (op, arg) in enumerate(code)
                        if isinstance(op, Label)
                    }

        # sf_targets are the targets of SETUP_FINALLY opcodes. They are
        # recorded because they have special stack behaviour. If an exception
        # was raised in the block pushed by a SETUP_FINALLY opcode, the block
        # is popped and 3 objects are pushed. On return or continue, the
        # block is popped and 2 objects are pushed. If nothing happened, the
        # block is popped by a POP_BLOCK opcode and 1 object is pushed by a
        # (LOAD_CONST, None) operation.
        #
        # In Python 3, the targets of SETUP_WITH have similar behavior,
        # complicated by the fact that they also have an __exit__ method
        # stacked and what it returns determines what they pop. So their
        # stack depth is one greater, a fact we are going to ignore for the
        # time being :-/
        #
        # Our solution is to record the stack state of SETUP_FINALLY targets
        # as having 3 objects pushed, which is the maximum. However, to make
        # stack recording consistent, the get_next_stacks function will always
        # yield the stack state of the target as if 1 object was pushed, but
        # this will be corrected in the actual stack recording.

        sf_targets = set( label_pos[arg]
                          for op, arg in code
                          if op == SETUP_FINALLY or op == SETUP_WITH
                        )

        # What we compute - for each opcode, its stack state, as an n-tuple.
        # n is the number of blocks pushed. For each block, we record the number
        # of objects pushed.
        stacks = [None] * len(code)

        def get_next_stacks(pos, curstack):
            """
            Get a code position and the stack state before the operation
            was done, and yield pairs (pos, curstack) for the next positions
            to be explored - those are the positions to which you can get
            from the given (pos, curstack).

            If the given position was already explored, nothing will be yielded.
            """
            op, arg = code[pos]

            if isinstance(op, Label):
                # We should check if we already reached a node only if it is
                # a label.

                if pos in sf_targets:
                    # Adjust a SETUP_FINALLY from 1 to 3 stack entries.
                    curstack = curstack[:-1] + (curstack[-1] + 2,)

                if stacks[pos] is None:
                    stacks[pos] = curstack
                else:
                    if stacks[pos] != curstack:
                        raise ValueError("Inconsistent code")
                    return

            def newstack(n):
                # Return a new stack, modified by adding n elements to the last
                # block
                if curstack[-1] + n < 0:
                    raise ValueError("Popped a non-existing element")
                return curstack[:-1] + (curstack[-1]+n,)

            if not isopcode(op):
                # label or SetLineno - just continue to next line
                yield pos+1, curstack

            elif op in ( RETURN_VALUE, RAISE_VARARGS ):
                # No place in particular to continue to
                pass

            elif op in (JUMP_FORWARD, JUMP_ABSOLUTE):
                # One possibility for a jump
                yield label_pos[arg], curstack

            elif op in (POP_JUMP_IF_FALSE, POP_JUMP_IF_TRUE):
                # Two possibilities for a jump
                yield label_pos[arg], newstack(-1)
                yield pos+1, newstack(-1)

            elif op in (JUMP_IF_TRUE_OR_POP, JUMP_IF_FALSE_OR_POP):
                # Two possibilities for a jump
                yield label_pos[arg], curstack
                yield pos+1, newstack(-1)

            elif op == FOR_ITER:
                # FOR_ITER pushes next(TOS) on success, and pops TOS and jumps
                # on failure
                yield label_pos[arg], newstack(-1)
                yield pos+1, newstack(1)

            elif op == BREAK_LOOP:
                # BREAK_LOOP goes to the end of a loop and pops a block
                # but like RETURN_VALUE we have no instruction position
                # to give. For now treat like RETURN_VALUE
                pass

            elif op == CONTINUE_LOOP:
                # CONTINUE_LOOP jumps to the beginning of a loop which should
                # already have been discovered. It does not change the stack
                # state nor does it create or pop a block.
                #yield label_pos[arg], curstack
                #yield label_pos[arg], curstack[:-1]
                pass

            elif op == SETUP_LOOP:
                # We continue with a new block.
                # On break, we jump to the label and return to current stack
                # state.
                yield label_pos[arg], curstack
                yield pos+1, curstack + (0,)

            elif op == SETUP_EXCEPT:
                # We continue with a new block.
                # On exception, we jump to the label with 3 extra objects on
                # stack
                yield label_pos[arg], newstack(3)
                yield pos+1, curstack + (0,)

            elif op == SETUP_FINALLY or op == SETUP_WITH :
                # We continue with a new block.
                # On exception, we jump to the label with 3 extra objects on
                # stack, but to keep stack recording consistent, we behave as
                # if we add only 1 object. Extra 2 will be added to the actual
                # recording.
                yield label_pos[arg], newstack(1)
                yield pos+1, curstack + ( int(op == SETUP_WITH) ,)

            elif op == POP_BLOCK:
                # Just pop the block
                yield pos+1, curstack[:-1]

            elif op == END_FINALLY :
                # Since stack recording of SETUP_FINALLY targets is of 3 pushed
                # objects (as when an exception is raised), we pop 3 objects.
                yield pos+1, newstack(-3)

            elif op == _WITH_CLEANUP_OPCODE:
                # Since WITH_CLEANUP[_START] is always found after SETUP_FINALLY
                # targets, and the stack recording is that of a raised
                # exception, we can simply pop 1 object and let END_FINALLY
                # pop the remaining 3.
                yield pos+1, newstack(-1)

            else:
                # nothing special, use the CPython value
                yield pos+1, newstack( stack_effect( op, arg ) )


        # Now comes the calculation: open_positions holds positions which are
        # yet to be explored. In each step we take one open position, and
        # explore it by appending the positions to which it can go, to
        # open_positions. On the way, we update maxsize.
        #
        # open_positions is a list of tuples: (pos, stack state)
        #
        # Sneaky Python coding trick here. get_next_stacks() is a generator,
        # it contains yield statements. So when we call get_next_stacks()
        # what is returned is an iterator. However, the yield statements in
        # get_next_stacks() are not in a loop as usual; rather it is
        # straight-line code that will execute 0, 1 or 2 yields depending on
        # the Opcode at pos.
        #
        # the list.extend() method takes an iterator and exhausts it, adding
        # all yielded values to the list. Hence the statement
        #
        #   open_positions.extend(get_next_stacks(pos,curstack))
        #
        # appends 0, 1 or 2 tuples (pos, stack_state) to open_positions.

        maxsize = 0
        open_positions = [(0, (0,))]
        while open_positions:
            pos, curstack = open_positions.pop()
            maxsize = max(maxsize, sum(curstack))
            open_positions.extend(get_next_stacks(pos, curstack))

        return maxsize