erg/doc/JA/python/bytecode_instructions.md
2024-05-18 18:42:41 +09:00

4.7 KiB

Pythonバイトコード命令

badge

Python bytecodeの変数操作系の命令はnamei (name index)を通してアクセスされる。これは、Pythonの動的変数アクセス(evalなどを使い、文字列でアクセスできる)を実現するためである。 1命令は2byteで、命令、引数がlittle endianで格納されている。 引数を取らない命令も2byte使っている(引数部は0)。

  • 3.11での変更: 命令は固定長ではなくなり、一部の命令が2バイトを超えることがある。余計に入ったバイト列は殆どの場合が0であり、その目的は不明だが、最適化オプションが入るのではないかと考えられる。判明している変則バイト長命令は以下の通り。
    • PRECALL (4 byte)
    • CALL (10 byte)
    • BINARY_OP (4 byte)
    • STORE_ATTR (10 byte)
    • COMPARE_OP (6 byte)
    • LOAD_GLOBAL (12 byte)
    • LOAD_ATTR (10 byte)
    • BINARY_SUBSCR (8 byte)

STORE_NAME(namei)

globals[namei] = stack.pop()

LOAD_NAME(namei)

stack.push(globals[namei])

トップレベルでしか呼び出されない。

LOAD_GLOBAL(namei)

stack.push(globals[namei])

トップレベルでSTORE_NAMEしたものを内側のスコープでLoadするためのものだが、トップレベルでのnameiならばあるスコープのコードオブジェクトでのnameiとも同じとは限らない(nameiではなくnameが同じ)

LOAD_CONST(namei)

stack.push(consts[namei])

定数テーブルにある定数をロードする。 現在(Python 3.9)のところ、CPythonではいちいちラムダ関数を"<lambda>"という名前でMAKE_FUNCTIONしている

>>> dis.dis("[1,2,3].map(lambda x: x+1)")
1       0 LOAD_CONST               0 (1)
        2 LOAD_CONST               1 (2)
        4 LOAD_CONST               2 (3)
        6 BUILD_LIST               3
        8 LOAD_ATTR                0 (map)
        10 LOAD_CONST               3 (<code object <lambda> at 0x7f272897fc90, file "<dis>", line 1>)
        12 LOAD_CONST               4 ('<lambda>')
        14 MAKE_FUNCTION            0
        16 CALL_FUNCTION            1
        18 RETURN_VALUE

STORE_FAST(namei)

fastlocals[namei] = stack.pop()

おそらくトップレベルにおけるSTORE_NAMEに対応する 参照のない(もしくは単一)変数がこれによって格納されると思われる わざわざグローバル空間が独自の命令を持っているのは最適化のため?

LOAD_FAST(namei)

stack.push(fastlocals[namei])

fastlocalsはvarnames?

LOAD_CLOSURE(namei)

cell = freevars[namei]
stack.push(cell)

そのあとBUILD_TUPLEが呼ばれている クロージャの中でしか呼び出されないし、cellvarsはクロージャの中での参照を格納するものと思われる LOAD_DEREFと違ってcell(参照を詰めたコンテナ)ごとスタックにpushする

STORE_DEREF(namei)

cell = freevars[namei]
cell.set(stack.pop())

内側のスコープで参照のない変数はSTORE_FASTされるが、参照される変数はSTORE_DEREFされる Pythonではこの命令内で参照カウントの増減がされる

LOAD_DEREF(namei)

cell = freevars[namei]
stack.push(cell.get())

名前リスト

varnames

fast_localsに対応する、関数の内部変数の名前リスト namesで同名の変数があっても、基本的に同じものではない(新しく作られ、そのスコープからは外の変数にアクセスできない) つまり、スコープ内で定義された外部参照のない変数はvarnamesに入る

names

globalsに対応 スコープ内で使われた外部定数(参照だけしている)の名前リスト(トップレベルでは普通の変数でもnamesに入る) つまり、スコープ外で定義された定数はnamesに入る

free variable

freevarsに対応 クロージャがキャプチャした変数。同じ関数インスタンス内においてstaticな振る舞いをする。

cell variables

cellvarsに対応 関数内で内側のクロージャ関数にキャプチャされる変数。コピーが作られるので、元の変数はそのまま。