Pythonを読む
PyAST_CompileObject (Python/compile.c) †
ASTができたので続いてバイトコード生成です。トップレベルのPyAST_CompileObjectを眺めるとPySymtable_BuildObjectを呼び出した後にcompiler_modを呼び出しています。というかファイルの先頭にステップが書いてあるので見ていましょう。
1
2
3
4
5
6
7
8
9
10
11
12
| -
|
|
|
|
|
|
|
|
|
|
!
|
|
というわけでまずシンボルテーブルを作るようです、
PySymtable_BuildObject (Python/symtable.c) †
PySymtable_BuildObjectを見ていくとまずtopを引数にsymtable_enter_blockを読んでいます。topは名前通りトップレベルなのでしょう。下を見るとsymtable_exit_blockがあるのでブロックに入る前にenterしてブロックが終わったらexitしているようです(そのまんまですが)
symtable_enter_blockを見てみます。
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
|
-
|
|
|
|
|
-
|
|
!
|
|
|
|
|
|
-
-
|
!
!
|
!
| static int
symtable_enter_block(struct symtable *st, identifier name, _Py_block_ty block,
void *ast, int lineno, int col_offset)
{
PySTEntryObject *prev = NULL, *ste;
ste = ste_new(st, name, block, ast, lineno, col_offset);
if (ste == NULL)
return 0;
if (PyList_Append(st->st_stack, (PyObject *)ste) < 0) {
Py_DECREF(ste);
return 0;
}
prev = st->st_cur;
Py_DECREF(ste);
st->st_cur = ste;
if (block == ModuleBlock)
st->st_global = st->st_cur->ste_symbols;
if (prev) {
if (PyList_Append(prev->ste_children, (PyObject *)ste) < 0) {
return 0;
}
}
return 1;
}
|
STEntryという名前にだまされ気味ですが、このオブジェクトはシンボルテーブルの1エントリではなく、あるブロックに存在する全変数を保持しています。ste_newを載せると長いので要点だけ抜き出すと、
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
-
|
|
|
|
|
|
|
|
|
!
| static PySTEntryObject *
ste_new(struct symtable *st, identifier name, _Py_block_ty block,
void *key, int lineno, int col_offset)
{
PySTEntryObject *ste = NULL;
PyObject *k = NULL;
k = PyLong_FromVoidPtr(key);
ste = PyObject_New(PySTEntryObject, &PySTEntry_Type);
ste->ste_id = k;
ste->ste_symbols = PyDict_New();
PyDict_SetItem(st->st_blocks, ste->ste_id, (PyObject *)ste);
return ste;
}
|
PyObjectとか出てきてますが深くは気にしません。とりあえずシンボルテーブルにブロックが登録されているということだけ覚えておきます。
symtable_visit_stmt †
さて、というわけでsymtableの仕組みがわかったので次に実際にシンボルテーブルにシンボルが登録されていく様子を見てきましょう。PySymtable_BuildObjectではsymtable_visit_stmtが呼び出されています。symtable_visit_stmtの中身はswitch文、今回は見ませんが関数定義やクラス定義でsymtable_enter_blockを呼んでおり新しいブロックを作成しています。
対象としているASTは以下。
Expr
ListComp
Name(n) Load
Comprephension
Name(n) Store
Call
Name(Range) Load
Num(10)
Compare
BinOp
Mult
Name(n) Load
Name(n) Load
Gt
Num(10)
Exprなので以下のコードが実行されます。
1
2
3
|
| case Expr_kind:
VISIT(st, expr, s->v.Expr.value);
break;
|
なんとなくわかりますが一応VISITの定義を見ると、
1
2
3
|
| #define VISIT(ST, TYPE, V) \
if (!symtable_visit_ ## TYPE((ST), (V))) \
VISIT_QUIT((ST), 0);
|
というわけで、symtable_visit_exprに続く。symtable_visit_exprもswitch文です。ListCompなのでsymtable_visit_listcompに進んで各種comprehensionを処理しているsymtable_handle_comprehensionへ。
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
|
-
|
|
|
|
|
|
|
|
|
-
|
!
|
|
-
|
|
!
|
-
|
|
!
|
|
|
|
|
|
|
!
| static int
symtable_handle_comprehension(struct symtable *st, expr_ty e,
identifier scope_name, asdl_seq *generators,
expr_ty elt, expr_ty value)
{
int is_generator = (e->kind == GeneratorExp_kind);
int needs_tmp = !is_generator;
comprehension_ty outermost = ((comprehension_ty)
asdl_seq_GET(generators, 0));
VISIT(st, expr, outermost->iter);
if (!scope_name ||
!symtable_enter_block(st, scope_name, FunctionBlock, (void *)e,
e->lineno, e->col_offset)) {
return 0;
}
st->st_cur->ste_generator = is_generator;
if (!symtable_implicit_arg(st, 0)) {
symtable_exit_block(st, (void *)e);
return 0;
}
if (needs_tmp && !symtable_new_tmpname(st)) {
symtable_exit_block(st, (void *)e);
return 0;
}
VISIT(st, expr, outermost->target);
VISIT_SEQ(st, expr, outermost->ifs);
VISIT_SEQ_TAIL(st, comprehension, generators, 1);
if (value)
VISIT(st, expr, value);
VISIT(st, expr, elt);
return symtable_exit_block(st, (void *)e);
}
|
outermostはComprephensionでiterはCall、targetはName(n)、ifsはCompareです。また、eltもName(n)です(ListComp直下の方)。ここでポイントなのは内包表記はFunctionのブロックが新しく作られるということ、テンポラリな名前が割り当てられていることです。symtable_new_tmpnameを見るとわかりますがその名前は_[0]なようです。
symtable_add_def †
さて、というわけで内包表記の各要素についてVISITされ最終的にsymtable_visit_exprでNameが処理されます。
1
2
3
|
| case Name_kind:
if (!symtable_add_def(st, e->v.Name.id, e->v.Name.ctx == Load ? USE : DEF_LOCAL))
VISIT_QUIT(st, 0);
|
コンテキストがLoadの場合はUSEを設定、それ以外(Storeなど)の場合はローカル変数として定義、と読み取れます。
symtable_add_defは以下のような感じでシンボルテーブルに名前が登録されます。
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
|
-
|
|
|
|
|
|
-
|
|
!
|
|
-
|
|
!
|
|
|
|
|
|
|
|
!
| static int
symtable_add_def(struct symtable *st, PyObject *name, int flag)
{
PyObject *o;
PyObject *dict;
long val;
PyObject *mangled = _Py_Mangle(st->st_private, name);
dict = st->st_cur->ste_symbols;
if ((o = PyDict_GetItem(dict, mangled))) {
val = PyLong_AS_LONG(o);
val |= flag;
} else
val = flag;
o = PyLong_FromLong(val);
if (PyDict_SetItem(dict, mangled, o) < 0) {
Py_DECREF(o);
goto error;
}
Py_DECREF(o);
Py_DECREF(mangled);
return 1;
error:
Py_DECREF(mangled);
return 0;
}
|
最終的にシンボルテーブルには以下のようにブロックが登録されます。listcompはtopの子供になります。
- top
- listcomp
- _[0] DEF_LOCAL
- n USE, DEF_LOCAL
symtable_analyze †
シンボルテーブルができたら次にsymtable_analyzeが呼ばれています。symtable_analyzeで何をやっているかの説明は400行目ぐらい(3.5.1の場合)に書いてあります。
1
2
3
4
5
6
7
| -
|
|
|
|
|
!
|
|
freeというのがいまいちわかりません。コードを見ていくことにしましょう。symtable_analyzeはトップレベルのSTEntryに対してanalyze_blockを呼んでいるだけです。analze_blockは長いので要点だけ取り出すと、
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
-
|
|
|
|
|
|
|
|
|
-
|
|
|
|
|
!
|
|
|
|
-
|
|
|
!
|
|
-
|
-
|
|
!
|
-
|
|
!
|
|
|
!
|
-
|
|
|
|
!
|
-
|
|
|
|
|
|
|
|
!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
| static int
analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free,
PyObject *global)
{
PyObject *name, *v, *local = NULL, *scopes = NULL, *newbound = NULL;
PyObject *newglobal = NULL, *newfree = NULL, *allfree = NULL;
PyObject *temp;
int i, success = 0;
Py_ssize_t pos = 0;
local = PySet_New(NULL);
scopes = PyDict_New();
newglobal = PySet_New(NULL);
newfree = PySet_New(NULL);
newbound = PySet_New(NULL);
while (PyDict_Next(ste->ste_symbols, &pos, &name, &v)) {
long flags = PyLong_AS_LONG(v);
if (!analyze_name(ste, scopes, name, flags, bound, local, free, global))
goto error;
}
if (ste->ste_type != ClassBlock) {
if (ste->ste_type == FunctionBlock) {
temp = PyNumber_InPlaceOr(newbound, local);
Py_DECREF(temp);
}
if (bound) {
temp = PyNumber_InPlaceOr(newbound, bound);
Py_DECREF(temp);
}
temp = PyNumber_InPlaceOr(newglobal, global);
Py_DECREF(temp);
}
allfree = PySet_New(NULL);
for (i = 0; i < PyList_GET_SIZE(ste->ste_children); ++i) {
PyObject *c = PyList_GET_ITEM(ste->ste_children, i);
PySTEntryObject* entry;
entry = (PySTEntryObject*)c;
if (!analyze_child_block(entry, newbound, newfree, newglobal, allfree))
goto error;
if (entry->ste_free || entry->ste_child_free)
ste->ste_child_free = 1;
}
temp = PyNumber_InPlaceOr(newfree, allfree);
Py_DECREF(temp);
if (ste->ste_type == FunctionBlock && !analyze_cells(scopes, newfree))
goto error;
if (!update_symbols(ste->ste_symbols, scopes, bound, newfree,
ste->ste_type == ClassBlock))
goto error;
temp = PyNumber_InPlaceOr(free, newfree);
Py_DECREF(temp);
success = 1;
error:
Py_XDECREF(scopes);
Py_XDECREF(local);
Py_XDECREF(newbound);
Py_XDECREF(newglobal);
Py_XDECREF(newfree);
Py_XDECREF(allfree);
return success;
}
|
削っても長いですが、
- ブロックにあるシンボルを収集して(analyze_name)
- 子シンボルにその情報を渡して解析(analyze_child_block)
- 最後に情報を更新(update_symbols)
ということをやっているようです。まだよくわからないので先に進みましょう。
analyze_name †
まず、analyze_name。global指定されている場合の処理とかが行われていますがサクッと無視して、
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| -
|
|
|
|
|
|
!
-
|
|
|
!
-
|
|
|
!
| if (flags & DEF_BOUND) {
SET_SCOPE(scopes, name, LOCAL);
if (PySet_Add(local, name) < 0)
return 0;
if (PySet_Discard(global, name) < 0)
return 0;
return 1;
}
if (bound && PySet_Contains(bound, name)) {
SET_SCOPE(scopes, name, FREE);
ste->ste_free = 1;
return PySet_Add(free, name) >= 0;
}
SET_SCOPE(scopes, name, GLOBAL_IMPLICIT);
|
DEF_BOUNDなんてセットしているところないぞ?と思ったらInclude/symtable.hで次のように定義されていました。
1
|
| #define DEF_BOUND (DEF_LOCAL | DEF_PARAM | DEF_IMPORT)
|
というわけで内包表記内で定義されているnはlocalに設定されます。
analyze_blockおよび上記のコメントを参考にするとboundは親ブロックのローカル変数集合です。親ブロックの変数を参照しているとfreeという集合に入れられるようです。今回はありませんが、
1
2
|
| a = [1, 2, 3]
[e * e for e in a]
|
みたいに書くとaがfreeになるようです。
rangeはGLOBAL_IMPLICITになります。言語仕様を考えると当たり前ですがこれらのことが行われています。
analyze_child_blockでは子ブロック用にbound, free, globalを作ってanalyze_blockを再帰呼び出し、update_symbolsは判明したスコープを各シンボル情報を格納したdictに設定しています。
だいぶ長くなったのでいったんここまでで区切りとします。
|