Pythonを読む

_PyEval_EvalCodeWithName (Python/ceval.c)

バイトコードができたのでいよいよ実行です。バイトコードの実行のエントリーポイントはceval.cに書かれたPyEval_EvalCode、PyEval_EvalCodeEx、_PyEval_EvalCodeWithNameと呼ばれて行き、これがメインの処理を行っているようです。

さて、_PyEval_EvalCodeWithNameですが大部分は引数の処理を行っています。今回は引数はないのでサクッと無視すると、

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 
 
 
 
 
-
|
|
|
|
|
|
|
-
!
|
|
|
|
-
|
|
|
!
|
|
|
|
!
static PyObject *
_PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
           PyObject **args, int argcount, PyObject **kws, int kwcount,
           PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure,
           PyObject *name, PyObject *qualname)
{
    PyCodeObject* co = (PyCodeObject*)_co;
    PyFrameObject *f;
    PyObject *retval = NULL;
    PyThreadState *tstate = PyThreadState_GET();
 
    f = PyFrame_New(tstate, co, globals, locals);
    
    // 引数処理
    
    retval = PyEval_EvalFrameEx(f,0);
 
fail: /* Jump here from prelude on failure */
 
    /* decref'ing the frame can cause __del__ methods to get invoked,
       which can call back into Python.  While we're done with the
       current Python frame (f), the associated C stack is still in use,
       so recursion_depth must be boosted for the duration.
    */
    ++tstate->recursion_depth;
    Py_DECREF(f);
    --tstate->recursion_depth;
    return retval;
}

実行オブジェクトに対してフレームを割り当て、実行しています。

PyFrame_New (Objects/frameobject.c)

まずフレーム構造について確認します。PyFrameObjectの定義はInclude/frameobject.hにあります。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
-
|
|
|
|
|
|
|
-
|
!
|
|
|
-
|
|
|
|
|
!
|
|
|
|
|
|
|
|
|
|
!
typedef struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
 
        /* In a generator, we need to be able to swap between the exception
           state inside the generator and the exception state of the calling
           frame (which shouldn't be impacted when the generator "yields"
           from an except handler).
           These three fields exist exactly for that, and are unused for
           non-generator frames. See the save_exc_state and swap_exc_state
           functions in ceval.c for details of their use. */
    PyObject *f_exc_type, *f_exc_value, *f_exc_traceback;
    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;
 
    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
} PyFrameObject;

普通のフレーム構造です。少し特殊なのはf_localsplusですね。コメントによるとローカル変数とスタックに使われるようです。

ではPyFame_Newを見てみましょう。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 
 
 
-
|
|
|
|
|
-
!
-
|
|
|
!
-
|
|
|
|
-
|
!
-
|
|
|
-
|
|
!
|
!
|
|
|
|
|
|
|
|
|
!
|
|
|
|
|
|
|
|
|
|
-
|
|
!
-
|
|
|
|
!
|
|
|
|
|
|
|
|
|
!
PyFrameObject *
PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals,
            PyObject *locals)
{
    PyFrameObject *back = tstate->frame;
    PyFrameObject *f;
    PyObject *builtins;
    Py_ssize_t i;
 
    // builtinsの処理。省略
    
    if (code->co_zombieframe != NULL) {
        f = code->co_zombieframe;
        code->co_zombieframe = NULL;
        _Py_NewReference((PyObject *)f);
    }
    else {
        Py_ssize_t extras, ncells, nfrees;
        ncells = PyTuple_GET_SIZE(code->co_cellvars);
        nfrees = PyTuple_GET_SIZE(code->co_freevars);
        extras = code->co_stacksize + code->co_nlocals + ncells + nfrees;
        if (free_list == NULL) {
            f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type, extras);
        }
        else {
            --numfree;
            f = free_list;
            free_list = free_list->f_back;
            if (Py_SIZE(f) < extras) {
                PyFrameObject *new_f = PyObject_GC_Resize(PyFrameObject, f, extras);
                f = new_f;
            }
            _Py_NewReference((PyObject *)f);
        }
 
        f->f_code = code;
        extras = code->co_nlocals + ncells + nfrees;
        f->f_valuestack = f->f_localsplus + extras;
        for (i=0; if_localsplus[i] = NULL;
        f->f_locals = NULL;
        f->f_trace = NULL;
        f->f_exc_type = f->f_exc_value = f->f_exc_traceback = NULL;
    }
    f->f_stacktop = f->f_valuestack;
    f->f_builtins = builtins;
    Py_XINCREF(back);
    f->f_back = back;
    Py_INCREF(code);
    Py_INCREF(globals);
    f->f_globals = globals;
    /* Most functions have CO_NEWLOCALS and CO_OPTIMIZED set. */
    if ((code->co_flags & (CO_NEWLOCALS | CO_OPTIMIZED)) == (CO_NEWLOCALS | CO_OPTIMIZED))
        ; /* f_locals = NULL; will be set by PyFrame_FastToLocals() */
    else if (code->co_flags & CO_NEWLOCALS) {
        locals = PyDict_New();
        f->f_locals = locals;
    }
    else {
        if (locals == NULL)
            locals = globals;
        Py_INCREF(locals);
        f->f_locals = locals;
    }
 
    f->f_lasti = -1;
    f->f_lineno = code->co_firstlineno;
    f->f_iblock = 0;
    f->f_executing = 0;
    f->f_gen = NULL;
 
    _PyObject_GC_TRACK(f);
    return f;
}

エラー処理とかはさっくり省いて、効率アップのためにフレームの生成が分岐しています。

  1. 前にそのCodeObjectを実行したフレーム(zombieframe)があればそれを使う
  2. free_listにフレームがあればそれを使う(スタックが必要とされる数以下なら確保を行う)
  3. zombieもfree_listもなければ生成を行う

PyObject_GC_NewVarはややこしいのですが、これはInclude/objimpl.hにあるマクロで、Modules/gcmodule.cにある_PyObject_GC_NewVarが関数の本体です。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 
 
-
|
|
|
|
|
|
|
|
!
PyVarObject *
_PyObject_GC_NewVar(PyTypeObject *tp, Py_ssize_t nitems)
{
    size_t size;
    PyVarObject *op;
 
    size = _PyObject_VAR_SIZE(tp, nitems);
    op = (PyVarObject *) _PyObject_GC_Malloc(size);
    if (op != NULL)
        op = PyObject_INIT_VAR(op, tp, nitems);
    return op;
}

_PyObject_VAR_SIZEはInclude/objimpl.hにあります。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
 
 
 
 
#define _PyObject_VAR_SIZE(typeobj, nitems)     \
    _Py_SIZE_ROUND_UP((typeobj)->tp_basicsize + \
        (nitems)*(typeobj)->tp_itemsize,        \
        SIZEOF_VOID_P)

TypeObjectについては詳しく見ませんがPyFrame_Typeの場合以下のようになっています。(Objects/frameobject.cに書かれています)

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
-
|
|
|
|
-
!
PyTypeObject PyFrame_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "frame",
    sizeof(PyFrameObject),
    sizeof(PyObject *),
    // 省略
};

sizeof(PyFrameObject)がtp_basicsize、sizeof(PyObject *)がtp_itemsizeです。つまり、PyFrameObjectの後にPyObject*の配列の領域が確保されることになり、PyFrameObjectの最終メンバーf_localsplusで参照できるということになります。

PyEval_EvalFrameEx (Python/ceval.c)

フレームについて確認できたのでいよいよコードの実行です。

さて、実行方法ですが、よくあるダイレクトスレッデッドコードです。ダイレクトスレッデッドコードって何?という人向けに大まかに説明すると、

  • 各命令に対応するswitchのcaseを用意し、ループをひたすら回す
  • でもそれだとループする分遅くなってしまうので、次の命令に直接gotoする(命令と実行アドレスの対応表を作っておく)

というものです。PyEval_EvalFrameExの上の方にその説明および実装(マクロ)が書かれています。

後、ここまで見てきて想像はつくと思いますがPythonのVMはスタックマシンのようです。これらがわかると、後は各命令コードについて見ていくだけです。というわけでいつものようにコードを実行してみます。

LOAD_CONST

LOAD_CONSTは定数をスタックにプッシュしています。ここでいう定数というのは数値や文字列以外に実行コードも含まれるようです。コード生成の際に内包表記のコードをLOAD_CONSTしてましたね。

LOAD_FAST

LOAD_FASTはローカル変数をスタックにプッシュしています。ただしこちらは引数がオブジェクト本体ではなくローカル変数領域の何番目に入っているかのインデックスになります。

LOAD_NAME

名前をローカル変数→グローバル変数→ビルトインの順番で検索し、見つかったものをスタックにプッシュしています。今回の場合、rangeで使われていますが、これはビルトインです。

STORE_FAST

STORE_FASTはLOAD_FASTの逆、スタックのトップをローカル変数領域に保存しています。

BINARY_MULTIPLY

スタックの一つ目と二つ目を取ってきて掛け算して結果をスタックに戻します。普通のスタックマシンでの掛け算処理です。

COMPARE_OP

スタックの一つ目と二つ目の比較処理。どのような比較を行うかは引数になっててcmp_outcome関数で実際の比較処理を行うようです。cmp_outcomeは簡単な比較処理だけしてより詳細な比較が必要な場合はさらにPyObject_RichCompareへ。Rubyの場合はプリミティブは特別処理していた気がしますがPythonはnumpyとかあるし再定義前提で個別の処理はしていないのかな?よく思うとMULTIPLYもか。

BUILD_LIST

リストを作成しスタックにプッシュしています。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
-
|
|
|
-
|
|
!
|
|
!
TARGET(BUILD_LIST) {
    PyObject *list =  PyList_New(oparg);
    if (list == NULL)
        goto error;
    while (--oparg >= 0) {
        PyObject *item = POP();
        PyList_SET_ITEM(list, oparg, item);
    }
    PUSH(list);
    DISPATCH();
}

引数で指定された分スタックからポップし詰めていますが今回は引数の値は0なので空リストです。後ろから詰めているようなのでスタックには最終要素が一番上になるように置くようですね。

LIST_APPEND

引数で指定した位置にあるリストにスタックトップの要素を追加しています。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
-
|
|
|
|
|
|
|
|
|
!
TARGET(LIST_APPEND) {
    PyObject *v = POP();
    PyObject *list = PEEK(oparg);
    int err;
    err = PyList_Append(list, v);
    Py_DECREF(v);
    if (err != 0)
        goto error;
    PREDICT(JUMP_ABSOLUTE);
    DISPATCH();
}

コード生成の際にLIST_APPENDでは2が指定されていました。つまり、一番上にある要素をその下にあるリストに追加していたという意味だったようです。

POP_JUMP_IF_FALSE

スタックトップをポップしてFalseだったら指定されたラベル(にある命令コード)に飛ぶ、普通の分岐命令です。

JUMP_ABSOLUTE

説明する必要もないような絶対ジャンプです。

MAKE_FUNCTION

クロージャの処理とかしていますが今回は関係ないのでさっくり無視すると、

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 
-
|
|
|
|
|
|
|
|
!
TARGET(MAKE_FUNCTION)
_make_function: {
    PyObject *qualname = POP(); /* qualname */
    PyObject *code = POP(); /* code object */
    PyObject *func = PyFunction_NewWithQualName(code, f->f_globals, qualname);
    Py_DECREF(code);
    Py_DECREF(qualname);
 
    PUSH(func);
    DISPATCH();
}

使われている箇所は以下のような感じ。関数を作り、それをスタックに積むということをしているようです。

 LOAD_CONST 内包表記のコード
 LOAD_CONST <listcomp>
 MAKE_FUNCTION 0

CALL_FUNCTION

関数呼び出しです。(そのまんま)

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
-
|
|
|
|
|
|
|
|
!
TARGET(CALL_FUNCTION) {
    PyObject **sp, *res;
    sp = stack_pointer;
    res = call_function(&sp, oparg);
    stack_pointer = sp;
    PUSH(res);
    if (res == NULL)
        goto error;
    DISPATCH();
}

call_functionではCで実装された関数なのかPythonで書かれた関数なのか、引数の数や種類などによって関数の呼び出し方法が分かれています。 なお、呼び出す関数や関数の引数はスタックに積まれています。rangeを呼んでいる部分は以下のようになっていましたね。

 LOAD_NAME 0(range)
 LOAD_CONST 10
 CALL_FUNCTION 1

念のため確認すると、

 10
 range

というように引数が上、呼び出す関数が下に積まれています。

rangeの場合、関数のように見えますが実体はTypeObjectなので、do_call→PyObject_Callと呼び出しが進みます。

PyObject_CallはObjects/abstract.cにあります。TypeObjectのtp_callに登録されているものが呼び出されます。 さて、rangeの実装はObjects/rangeobject.cにあります。その中にPyRange_Typeの定義がありますがtp_callはNULLです。tp_callがNULLだとPyObject_Callはエラーになるのですが実際にはエラーにはなりません。そのからくりは、rangeがtypeのサブクラスのためです。

ちょっと横道にそれますが、ここら辺の処理は初めに深く追うのをやめたPy_Initialize以下で行われていました。_Py_InitializeEx_Private(Python/pylifecycle.c)→_Py_ReadyTypes(Objects/object.c)→PyType_Ready(object/typeobject.c)→inherit_slotsと進んでいき、サブクラスでメソッドが定義されていないのでスーパークラスのものが参照されるようになります。

というわけで、type_callが呼び出されるわけですが、ここでtp_newに指定された関数を呼び出しています。tp_newにはrange_newが指定されているので結果、range_newが呼び出されます。

GET_ITER

というわけでスタックにrangeオブジェクトが積まれました。生成されたコードではその後、GET_ITERを呼び出しています。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
-
|
|
|
|
|
|
|
|
|
!
TARGET(GET_ITER) {
    /* before: [obj]; after [getiter(obj)] */
    PyObject *iterable = TOP();
    PyObject *iter = PyObject_GetIter(iterable);
    Py_DECREF(iterable);
    SET_TOP(iter);
    if (iter == NULL)
        goto error;
    PREDICT(FOR_ITER);
    DISPATCH();
}

rangeオブジェクトがrangeオブジェクトのイテレータオブジェクトに置き換わりました。

CALL_FUNCTION(Pythonでの実装の場合)

GET_ITER後、再度CALL_FUNCTIONが実行されています。

 GET_ITER
 CALL_FUNCTION 1

スタックトップにはイテレータオブジェクト、その下には内包表記のコードが置かれています。というわけで、イテレータオブジェクトを引数に内包表記のコードが呼び出されます。

さて、内包表記のコードはPythonで書かれているので先ほどとは呼び出し方法が違います。具体的には、call_functionからfast_functionに処理が進みます。処理が分岐はしていますがいずれにしろ新しいフレームが作成され、関数コードの実行が行われます。

FOR_ITER

内包表記のコードではFOR_ITERがポイントになります。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
-
|
|
|
-
|
|
|
|
!
-
|
|
|
|
|
!
|
|
|
|
|
!
TARGET(FOR_ITER) {
    /* before: [iter]; after: [iter, iter()] *or* [] */
    PyObject *iter = TOP();
    PyObject *next = (*iter->ob_type->tp_iternext)(iter);
    if (next != NULL) {
        PUSH(next);
        PREDICT(STORE_FAST);
        PREDICT(UNPACK_SEQUENCE);
        DISPATCH();
    }
    if (PyErr_Occurred()) {
        if (!PyErr_ExceptionMatches(PyExc_StopIteration))
            goto error;
        else if (tstate->c_tracefunc != NULL)
            call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj, tstate, f);
        PyErr_Clear();
    }
    /* iterator ended normally */
    STACKADJ(-1);
    Py_DECREF(iter);
    JUMPBY(oparg);
    DISPATCH();
}

FOR_ITERの使われてるところは以下のようになっています。

 BUILD_LIST 0
 LOAD_FAST 0
 :start
 FOR_ITER REL:anchor
 :
 STORE_FAST 0(n)
 LOAD_FAST 0(n)
 LOAD_FAST 0(n)
 BINARY_MULTIPLY
 LOAD_CONST 10
 COMPARE_OP >
 POP_JUMP_IF_FALSE ABS:if_cleanup
 :
 LOAD_FAST 0(n)
 LIST_APPEND 2
 :skip
 :if_cleanup
 JUMP_ABSOLUTE ABS:start
 :anchor
 RETURN_VALUE

LOAD_FASTで渡された引数(イテレータオブジェクト)をスタックに積み、それから要素を取得して処理、要素がもうなくなったら引数で指定されたところまでジャンプするということを行っています。結果、繰り返しが終わるとRETURN_VALUEが実行されることになります。

RETURN_VALUE

スタックトップを戻り値とし、PyEval_EvalFrameExを終了します。今回の場合で言うと、スタックトップに残っているのは内包表記で作成したリストですね。これでトップレベルに戻り、作成されたリストが改めてスタックに積まれ、最後にPRINT_EXPRが呼び出されることで画面への出力が行われます。

おわりに

というわけで、シェルで

Everything is expanded.Everything is shortened.
  1
 
[n for n in range(10) if n * n > 10]

と打った時にどのようなことが行われるかを見てきました。Rubyと似ているところもあれば違うところもありました。ツリー作成が二段階なところ、シンボルテーブルを作るタイミング、呼び出す関数の指定、関数の実行方法などはRubyと異なっていたように思います。

それではみなさんもよいコードリーディングを。


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2016-12-31 (土) 17:42:16 (2673d)