Pythonのバイトコード一覧 (2)

つづきです。

ironoir.hatenablog.com

以下の出力をもう少し調べます。

cmp_op: ('<', '<=', '==', '!=', '>', '>=')
hasconst: [100]
hasfree: [135, 136, 137, 138, 148]
hasname: [90, 91, 95, 96, 97, 98, 101, 106, 108, 109, 116, 160]
hasjrel: [93, 110, 122, 143, 154]
hasjabs: [111, 112, 113, 114, 115, 121]
haslocal: [124, 125, 126]
hascompare: [107]

cmp_op以外は数値のリストなので、命令名に変換しましょう。

cmp_op: ('<', '<=', '==', '!=', '>', '>=')
hasconst: ['LOAD_CONST']
hasfree: ['LOAD_CLOSURE', 'LOAD_DEREF', 'STORE_DEREF', 'DELETE_DEREF', 'LOAD_CLASSDEREF']
hasname: ['STORE_NAME',
 'DELETE_NAME',
 'STORE_ATTR',
 'DELETE_ATTR',
 'STORE_GLOBAL',
 'DELETE_GLOBAL',
 'LOAD_NAME',
 'LOAD_ATTR',
 'IMPORT_NAME',
 'IMPORT_FROM',
 'LOAD_GLOBAL',
 'LOAD_METHOD']
hasjrel: ['FOR_ITER', 'JUMP_FORWARD', 'SETUP_FINALLY', 'SETUP_WITH', 'SETUP_ASYNC_WITH']
hasjabs: ['JUMP_IF_FALSE_OR_POP',
 'JUMP_IF_TRUE_OR_POP',
 'JUMP_ABSOLUTE',
 'POP_JUMP_IF_FALSE',
 'POP_JUMP_IF_TRUE',
 'JUMP_IF_NOT_EXC_MATCH']
haslocal: ['LOAD_FAST', 'STORE_FAST', 'DELETE_FAST']
hascompare: ['COMPARE_OP']

出てきましたね。名前から何をするのか想像がつくものが多いです。分類するためにもざっくり最初の単語だけ抜き取って並べてみましょう。

import dis
import pprint

# pprint.pprint(dis.opname)
# pprint.pprint(dis.opmap)

disassemble_attributes = [
    'cmp_op',
    'hasconst', 
    'hasfree',
    'hasname',
    'hasjrel',
    'hasjabs',
    'haslocal',
    'hascompare',
]

def print_disassemble_info(s):
    print(s, end=': ')
    info = getattr(dis, s)
    if isinstance(info[0], int):
        names = [dis.opname[code] for code in info]
        pprint.pprint(names)
    else:
        pprint.pprint(info)

def get_prefix(s):
    info = getattr(dis, s)
    if isinstance(info[0], int):
        return {dis.opname[code].split('_')[0] for code in info}
    else:
        return set()

def print_disassemble_info_all():
    prefixes = set()
    for attr in disassemble_attributes:
        print_disassemble_info(attr)
        prefixes |= get_prefix(attr)
    print('prefix', end=': ')
    pprint.pprint(prefixes)

print_disassemble_info_all()

集合の内包表記を久しぶりに使いました。重複を省く意図です。これはPythonならでは、ですよね。

prefix: {'DELETE', 'FOR', 'SETUP', 'STORE', 'POP', 'JUMP', 'LOAD', 'COMPARE', 'IMPORT'}

というわけで、上記がその結果です。いまいちピンとこないので同じことをdis.opmapからやってみます。

print(len(dis.opmap.keys()))

op_names = set()
for key in dis.opmap.keys():
    op_names |= {key.split('_')[0]}
pprint.pprint(op_names)
119
{'BEFORE',
 'BINARY',
 'BUILD',
 'CALL',
 'COMPARE',
 'CONTAINS',
 'DELETE',
 'DICT',
 'DUP',
 'END',
 'EXTENDED',
 'FOR',
 'FORMAT',
 'GET',
 'IMPORT',
 'INPLACE',
 'IS',
 'JUMP',
 'LIST',
 'LOAD',
 'MAKE',
 'MAP',
 'NOP',
 'POP',
 'PRINT',
 'RAISE',
 'RERAISE',
 'RETURN',
 'ROT',
 'SET',
 'SETUP',
 'STORE',
 'UNARY',
 'UNPACK',
 'WITH',
 'YIELD'}

うーん、全部で119命令の中から名前の最初の単語を抜き出してみましたが、意外と雑多。これはちゃんと命令ごとに見ていかないといけないかな。

一通り見てみたんですが、長くなってしまったので記事を分けます。独自に分類もしてみたので、それだけ箇条書きしてみます。それにしても、「コルーチン関連」とか、面白い命令がたくさんありますね。正直、分類の方法もいくつかありますね。もっと調べたいけど、それだと横道に逸れていく…

  • スタック操作
  • 数値演算
  • ビット演算
  • 論理演算
  • ロード操作
  • ストア操作
  • ジャンプ
  • コルーチン関連
  • 関数呼出/返却/例外
  • 演算(その他)
  • その他

もうすこしだけ、つづくんぢゃ…