[Python-Dev] Can someone explain the fast_block_end manipulation?

skip at pobox.com skip at pobox.com
Thu Jan 8 05:30:11 CET 2009


Everybody seems to be doing stuff with the virtual machine all of a sudden.
I thought I would get in on the fun.  I am generating functions from the
byte code which pretty much just inlines the C code implementing each
opcode.  The idea is to generate a C function that looks like a small
version of PyEval_EvalFrameEx but without the for loop and switch statement.
Instead it just contains the C code implementing the opcodes used in that
function.  For example, this function

    >>> def f(a):
    ...   for i in range(a):
    ...     x = i*i

disassembles to this:

  2           0 SETUP_LOOP              30 (to 33) 
              3 LOAD_GLOBAL              0 (range) 
              6 LOAD_FAST                0 (a) 
              9 CALL_FUNCTION            1 
             12 GET_ITER             
        >>   13 FOR_ITER                16 (to 32) 
             16 STORE_FAST               1 (i) 

  3          19 LOAD_FAST                1 (i) 
             22 LOAD_FAST                1 (i) 
             25 BINARY_MULTIPLY      
             26 STORE_FAST               2 (x) 
             29 JUMP_ABSOLUTE           13 
        >>   32 POP_BLOCK            
        >>   33 LOAD_CONST               0 (None) 
             36 RETURN_VALUE         

and compiles to this

    #include "opcode_mini.h"

    PyObject *
    _PyEval_EvalMiniFrameEx(PyFrameObject *f, int throwflag)
    {

            static int minime = 1;
            static int jitting = 1;

            /* most of the stuff at the start of PyEval_EvalFrameEx */
            PyEval_EvalFrameEx_PROLOG();

            /* code length=37 */
            /* nlabels=3, offsets: 13, 32, 33, */

            oparg = 30
            SETUP_LOOP_IMPL(oparg); /* 0 */
            oparg = 0
            LOAD_GLOBAL_IMPL(oparg, 0); /* 3 */
            oparg = 0
            LOAD_FAST_IMPL(oparg); /* 6 */
            oparg = 1
            CALL_FUNCTION_IMPL(oparg); /* 9 */
            GET_ITER_IMPL(); /* 12 */
    __L13:
            FOR_ITER_IMPL(__L32);
            oparg = 1
            STORE_FAST_IMPL(oparg); /* 16 */
            oparg = 1
            LOAD_FAST_IMPL(oparg); /* 19 */
            oparg = 1
            LOAD_FAST_IMPL(oparg); /* 22 */
            BINARY_MULTIPLY_IMPL(); /* 25 */
            oparg = 2
            STORE_FAST_IMPL(oparg); /* 26 */
            goto __L13;
    __L32:
            POP_BLOCK_IMPL(); /* 32 */
    __L33:
            oparg = 0
            LOAD_CONST_IMPL(oparg); /* 33 */
            RETURN_VALUE_IMPL(); /* 36 */

            /* most of the stuff at the end of PyEval_EvalFrameEx */
            PyEval_EvalFrameEx_EPILOG();
    }

Besides eliminating opcode decoding I figure it might give the compiler lots
of optimization opportunities.  Time will tell though.

I have just about everything implemented but I'm a bit stuck trying to
figure out how to deal with the block manipulation code in
PyEval_EvalFrameEx after the fast_block_end label.  JUMP* opcodes in the
interpreter turn into gotos in the generated code.  It seems I will have to
replace any JUMP instructions in the epilog with computed gotos.  In
particular, I am a little confused by this construct:

                        if (b->b_type == SETUP_LOOP && why == WHY_CONTINUE) {
                                /* For a continue inside a try block,
                                   don't pop the block for the loop. */
                                PyFrame_BlockSetup(f, b->b_type,
                                                   b->b_handler,
                                                   b->b_level); \
                                why = WHY_NOT;
                                JUMPTO(PyLong_AS_LONG(retval));
                                Py_DECREF(retval);
                                break;
                        }

The top of stack has been popped into retval.  I think that value was maybe
pushed here:

                        if (b->b_type == SETUP_FINALLY) {
                                if (why & (WHY_RETURN | WHY_CONTINUE))
                                        PUSH(retval);
                                PUSH(PyLong_FromLong((long)why));
                                why = WHY_NOT;
                                JUMPTO(b->b_handler);
                                break;
                        }

but I'm confused.  I don't see anyplace obvious where a value resembling a
jump offset or jump target was pushed onto the stack.  What's with that
first JUMPTO in the SETUP_LOOP/WHY_CONTINUE code?  Is the stack/block
cleanup code documented anywhere?  Wiki?  Pointers to python-dev threads?

I found this brief thread from last July:

    http://mail.python.org/pipermail/python-dev/2008-July/thread.html#81480

A svn annotate suggests that much of the fun in this code began with a
checkin by Jeremy Hylton (r19260).  It references an old SF patch (102989)
but I can't locate that in the current issue tracker to read the
discussion.  Is there some way I can retrieve that?  The obvious

    http://bugs.python.org/issue102989

didn't work for me.

Thx,

Skip


More information about the Python-Dev mailing list