CPython 3.2 SourceCodeReading

38
CPython 3.2 eval を追う Shinya Kawanaka (@mayahjp) Sunday, March 27, 2011

description

eval を追う

Transcript of CPython 3.2 SourceCodeReading

Page 1: CPython 3.2 SourceCodeReading

CPython 3.2eval を追うShinya Kawanaka (@mayahjp)

Sunday, March 27, 2011

Page 2: CPython 3.2 SourceCodeReading

誰?

• なまえ: 川中 真耶 (Shinya Kawanaka)

• ハンドル : mayah (or MAYAH)

• twitter: @mayahjp

• Python: 一応読める程度の能力 (書いたことはあまりない)

Sunday, March 27, 2011

Page 3: CPython 3.2 SourceCodeReading

今回の目標

• CPython の eval の部分を理解する

• 枝葉末節は追わないで、ざっくりと読む

Sunday, March 27, 2011

Page 4: CPython 3.2 SourceCodeReading

一般的なインタプリタでの

eval の実装• 抽象構文木 (AST) のノードに対して

• 現在の環境Γ(変数 → 値)の下

• より小さなノードがあれば評価

• それを組み合わせて自分自身のノードを評価

Γ ⊦ e(e1, ..., en) → v

Γ ⊦ e1 → v1, ..., Γ ⊦ en → vnΓ ⊦ e → f

Γ ⊦ f(v1, ..., vn) → v

テキトーな関数適用(副作用なし)の評価ルール

Sunday, March 27, 2011

Page 5: CPython 3.2 SourceCodeReading

AST と値と環境の実装がわかればあとは分かる

• この3つが分かれば、後は単に AST の各ノードがどのように eval されていくかを見ればなんとかなる

• ……が、Python がこうであるとは限らない

Sunday, March 27, 2011

Page 6: CPython 3.2 SourceCodeReading

Python でも分かるか?→ ALMOST YES

• ceval.c という如何にもそれっぽいソースファイルがありその中の関数 PyEval_EvalCode で、co が code (AST) で

globals や locals が環境だと思うとまさにそれ。

PyObject *PyEval_EvalCode(PyObject *co, PyObject *globals, PyObject *locals){ return PyEval_EvalCodeEx(co, globals, locals, (PyObject **)NULL, 0, (PyObject **)NULL, 0, (PyObject **)NULL, 0, NULL, NULL);}

Sunday, March 27, 2011

Page 7: CPython 3.2 SourceCodeReading

というわけで、まず AST と値と環境の実装を探ろう

• AST

• PyEvalCode の先をちょっと読む限りは PyCodeObject という型があり、それが

AST を表す型であるのは間違いなさそう

• 値

• PyObject じゃないということはなさそう

• 実際は PyObject を継承したような何かである可能性が高い

• 環境

• PyEvalCode に PyObject* で globals と locals が渡ってきているので、これらが環境であるはずだから、中身が map かなんかで実装されている可能性が高いと睨む

Sunday, March 27, 2011

Page 8: CPython 3.2 SourceCodeReading

AST / PyCodeObject (code.h)

• PyCodeObject は code.h に定義されている

• opcode の構造が分からないとつらいことが予想されるが...? 変数たちはPyObjectを詳細に見る必要あり

typedef struct { PyObject_HEAD ... <snip /> ... PyObject *co_code; /* instruction opcodes */ PyObject *co_consts; /* list (constants used) */ PyObject *co_names; /* list of strings (names used) */ PyObject *co_varnames; /* tuple of strings (local variable names) */ PyObject *co_freevars; /* tuple of strings (free variable names) */ PyObject *co_cellvars; /* tuple of strings (cell variable names) */ ... <snip /> ...} PyCodeObject;

Sunday, March 27, 2011

Page 9: CPython 3.2 SourceCodeReading

opcode (opcode.h)

• opcode.h に opcode は定義されていた

• 単なる define の羅列

#define MAKE_CLOSURE 134 /* same as MAKE_FUNCTION */#define LOAD_CLOSURE 135 /* Load free variable from closure */#define LOAD_DEREF 136 /* Load and dereference from closure cell */ #define STORE_DEREF 137 /* Store into cell */ #define DELETE_DEREF 138 /* Delete closure cell */

Sunday, March 27, 2011

Page 10: CPython 3.2 SourceCodeReading

opcode に対する動作を見たければ grep すればよい

• 本当か?

• してみた → 本当くさい

• 実際に ceval.c に巨大な eval ぽいものがあり、switch

case っぽい羅列がある

• これでとりあえず AST の根本的な部分は分かった

Sunday, March 27, 2011

Page 11: CPython 3.2 SourceCodeReading

AST まとめ• PyCodeObject

• co_code に opcode が入っている

• co_consts などに定数とか子供の AST とかが入っている

• AST と書いたが、実際は中間コードに落ちているぽい

Sunday, March 27, 2011

Page 12: CPython 3.2 SourceCodeReading

値 PyObject (object.h)

• GC 用の参照カウントが見える

• ob_type で PyObject の実際の型を表しているくさい

typedef struct _object { ... <snip /> ... Py_ssize_t ob_refcnt; struct _typeobject *ob_type;} PyObject;

typedef struct { PyObject ob_base; Py_ssize_t ob_size; /* Number of items in variable part */} PyVarObject;

Sunday, March 27, 2011

Page 13: CPython 3.2 SourceCodeReading

_typeobject (object.h)

• オブジェクトの型を表す

• コンストラクタを格納する変数などが見えるが、これ以上は必要時に追えば良さそう

typedef struct _typeobject { PyObject_VAR_HEAD ... <snip /> ... destructor tp_dealloc; printfunc tp_print; getattrfunc tp_getattr; setattrfunc tp_setattr; void *tp_reserved; /* formerly known as tp_compare */ reprfunc tp_repr;

Sunday, March 27, 2011

Page 14: CPython 3.2 SourceCodeReading

あれ?値はどこに格納するの?

• どっかに値を取ってくるインタフェースがあるはず

• _typeobject (PyTypeObject) 中の tp_dict がそれっぽい

• しかしこれは PyObject しか取れない

• でも、全ての値は primitive に最終的に行き着くはずなんだから primitive から PyObject が作れる必要があるはず

• 要するに、単なる int は何処に格納するの?

Sunday, March 27, 2011

Page 15: CPython 3.2 SourceCodeReading

PyLongObject (longobject.h)

• よく解らんけど PyなんとかObject として名前が定義されているみたいだから、PyIntObject とかないんか?

• → grep したらあったけど定義がねえ!

• 周辺を見ると PyLongObject が使われている

• 64bit 考えると long の方が正解くさいが...?

Sunday, March 27, 2011

Page 16: CPython 3.2 SourceCodeReading

PyLongObject (contd.)

• _longobject という名前で定義されている

• 定義見る限りは大当たり

• digit は(大体の環境では) uint32 で定義

• 64bit はどこへ?

struct _longobject { PyObject_VAR_HEAD digit ob_digit[1];};

Sunday, March 27, 2011

Page 17: CPython 3.2 SourceCodeReading

値 まとめ• PyObject がオブジェクト型(ぽい)

• 変数などは取得関数経由で PyObject 型で返る

• メソッドも一緒

• 全ての値は primitive の組み合わせで出来るんだから

primitive を表す PyObject が必要

• PyLongObject がそれ

Sunday, March 27, 2011

Page 18: CPython 3.2 SourceCodeReading

環境は後から分かる...?• 普通は map かなんかのハズだから大したことないはず

• PyObject が来てるんだから dict かなんかに入ってるんだろう

• こればっかりは eval の中に入ってみないと分からんね

• eval の変数から値を取得する部分とか読めば絶対環境に触っているから、そのへん見れば分かるだろう

Sunday, March 27, 2011

Page 19: CPython 3.2 SourceCodeReading

前置き終わり• とりあえず eval を読むのに必要そうな知識へのポインタまで獲得した

• あとは環境がどのようになっているのかを考えながら読んでいけば良い

Sunday, March 27, 2011

Page 20: CPython 3.2 SourceCodeReading

PyEval_EvalCodeEx (ceval.c)

• おおまかな流れ

• 環境+α (PyFrameObject) を作成 (PyFrame_New())

• 引数を処理して環境に追加

• PyFrameObject を PyEval_EvalFrameEx で評価

• PyEval_EvalFrameEx が実際に AST を見て eval をしているところ

Sunday, March 27, 2011

Page 21: CPython 3.2 SourceCodeReading

PyFrameObject (frameobject.h)

• 1つ前のフレーム、現在のコード、環境などが構造体としてまとまったもの

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 */ ....}

Sunday, March 27, 2011

Page 22: CPython 3.2 SourceCodeReading

PyFrame_New (frameobject.c)

• コードや変数からフレームを作成

• おおよそ構造体にコピーしているだけ

• フレームオブジェクトを作るとき

• ゾンビフレームというのが code にあればそれを使う

• なければ、フリーリストから取得

• それでもなければ malloc

Sunday, March 27, 2011

Page 23: CPython 3.2 SourceCodeReading

引数を環境に追加• 引数チェック(数があってるかなど)が主な処理

• 引数は local 変数の他に kw (keyword) などがあり、それが全部1つの関数に書かれているため、やたら処理があるように見える

• SETLOCAL マクロが引数を環境に追加、と思えば OK

for (i = 0; i < n; i++) { x = args[i]; Py_INCREF(x); SETLOCAL(i, x); }

Sunday, March 27, 2011

Page 24: CPython 3.2 SourceCodeReading

PyEval_EvalCodeEx (contd.)

• 引数をフレームに追加し終わったら

• PyEval_EvalFrameEx を呼んで実際に評価

• 途中で失敗すれば goto fail

• fail すると __del__ を呼ぶ必要があるので参照をデクリメント

• その間はフレームと C stack は使えるようになっている

Sunday, March 27, 2011

Page 25: CPython 3.2 SourceCodeReading

ところで PyEval_EvalCode

ってどこから呼ばれるのん?• pythonrun.c 中の run_mod() 関数

• AST コンパイルして eval という教科書すぎる実装

static PyObject * run_mod(mod_ty mod, const char *filename, PyObject *globals, PyObject *locals, PyCompilerFlags *flags, PyArena *arena){ PyCodeObject *co; PyObject *v; co = PyAST_Compile(mod, filename, flags, arena); if (co == NULL) return NULL; v = PyEval_EvalCode((PyObject*)co, globals, locals); Py_DECREF(co); return v;}

Sunday, March 27, 2011

Page 26: CPython 3.2 SourceCodeReading

fast_function (ceval.c)

• eval している別の関数

• 引数がスタックに直接載っている場合はこっちを呼ぶと処理があんまりなくて速い

• eval 中 call_function するときに呼ばれる可能性があるようだ

• まあ、あとから出てくるでしょう、多分。

Sunday, March 27, 2011

Page 27: CPython 3.2 SourceCodeReading

PyEval_EvalFrameEx (ceval.c)

• ここがメイン

• とりあえず重要そうなローカル変数を押さえておく

register int opcode; /* Current opcode */ register int oparg; /* Current opcode argument, if any */ register enum why_code why; /* Reason for block stack unwind */ register int err; /* Error status -- nonzero if error */ register PyObject *x; /* Result object -- NULL if error */ register PyObject *v; /* Temporary objects popped off stack */ register PyObject *w; register PyObject *u; register PyObject *t;

Sunday, March 27, 2011

Page 28: CPython 3.2 SourceCodeReading

PyEval_EvalFrameEx (contd.)

• 大量のマクロの定義に圧倒されるが読み飛ばして、Main

switch on opcode と書かれてあるところへ

• 最初のコメントに「失敗した operation は x を NULL 以外、もしくは err を非0、もしくは why を WHY_NOT 以外のどれかにしなければならない。また、成功した

operation はそうしてはならない」とある

Sunday, March 27, 2011

Page 29: CPython 3.2 SourceCodeReading

PyEval_EvalFrameEx (contd.)

• 一番簡単なのは NOP (No operation)

• マクロの定義

TARGET(NOP) FAST_DISPATCH();

#define TARGET(op) \ case op:

#define DISPATCH() continue

#define FAST_DISPATCH() goto fast_next_opcode

Sunday, March 27, 2011

Page 30: CPython 3.2 SourceCodeReading

PyEval_EvalFrameEx (contd.)

• NOP の次に LOAD_CONST (定数読み込み) があった

• 定数読み込みが PUSH(x) ってことは stack machine か?

x = GETITEM(consts, oparg); Py_INCREF(x); PUSH(x); FAST_DISPATCH();

#define BASIC_PUSH(v) (*stack_pointer++ = (v))#define PUSH(v) BASIC_PUSH(v)

Sunday, March 27, 2011

Page 31: CPython 3.2 SourceCodeReading

実際 stack machine だった• 仮に stack machine なら足し算 (BINARY_ADD) などの 2

operands の演算は POP → POP → PUSH みたいなハズ

• → あってる

TARGET(BINARY_ADD) // only main w = POP(); v = TOP(); x = PyNumber_Add(v, w); Py_DECREF(v); Py_DECREF(w); SET_TOP(x); if (x != NULL) DISPATCH(); break;

Sunday, March 27, 2011

Page 32: CPython 3.2 SourceCodeReading

後は何を見れば良い?• stack machine だということが分かったので、後は特徴的な命令を眺めれば十分

• if 式はどうなるか?

• 命令に if がなければ compare & branch が普通だが

• 関数呼び出し

• python のどういう AST に compile されるかも興味があるが今日の範囲からは外れるので略

Sunday, March 27, 2011

Page 33: CPython 3.2 SourceCodeReading

JUMP_IF_TRUE [FALSE] (_OR_POP)

• stack machine ならスタックのトップの値を見て jump するかしないか選べる命令があるはず

• JUMP_IF_TRUE [FALSE] (_OR_POP) がそれ

TARGET(POP_JUMP_IF_TRUE) w = POP(); if (w == Py_False) { Py_DECREF(w); FAST_DISPATCH(); } if (w == Py_True) { Py_DECREF(w); JUMPTO(oparg); // this will set the next instruction! FAST_DISPATCH();

Sunday, March 27, 2011

Page 34: CPython 3.2 SourceCodeReading

JUMPTO の実装は?• first_instr が code の先頭

• offset を足している

• first_instr は何処で定義されてる?

• co_code の中身はバイト列か、ということも分かる

#define JUMPTO(x) (next_instr = first_instr + (x))

first_instr = (unsigned char*) PyBytes_AS_STRING(co->co_code);

Sunday, March 27, 2011

Page 35: CPython 3.2 SourceCodeReading

関数呼出 CALL_FUNCTION

• 単に stack pointer をセットして call_function 関数を呼び出しているだけ

TARGET(CALL_FUNCTION) { PyObject **sp; PCALL(PCALL_ALL); // for profiling sp = stack_pointer; x = call_function(&sp, oparg); stack_pointer = sp; PUSH(x); if (x != NULL) DISPATCH(); break; }

Sunday, March 27, 2011

Page 36: CPython 3.2 SourceCodeReading

call_function

• oparg が関数の場所を示す

• 下 16bit はなんかタグ付けされてるのか歴史的事情のどちらか? (追ってない

static PyObject *call_function(PyObject ***pp_stack, int oparg){ int na = oparg & 0xff; int nk = (oparg>>8) & 0xff; int n = na + 2 * nk; PyObject **pfunc = (*pp_stack) - n - 1; PyObject *func = *pfunc; PyObject *x, *w;

Sunday, March 27, 2011

Page 37: CPython 3.2 SourceCodeReading

call_function (contd.)

• あとはこんな感じで呼んでいるだけか

• CPyFunction が最も呼ばれるという仮定があるので最初にチェックされる

PyObject *callargs; callargs = load_args(pp_stack, na); READ_TIMESTAMP(*pintr0); C_TRACE(x, PyCFunction_Call(func,callargs,NULL)); READ_TIMESTAMP(*pintr1); Py_XDECREF(callargs);

Sunday, March 27, 2011

Page 38: CPython 3.2 SourceCodeReading

TIME UP

• とりあえず、まあなんとなく読めた

Sunday, March 27, 2011