Python Bytecode短歌

64 00 00 0c 04
04 04 17 04 03 17 02
04 03 17 04 03
03 04 03 03 13 04 04
14 04 17 18 17 0b 53

空関数のco_codeをこれで置き換えて実行すると、2014と表示されます。

詳しい説明は後で。

味わい

04を「ど」03を「ろ」17を「あ」と発音するなら

64 00 00 0c ど
ど ど あ ど ろ あ 02
ど ろ あ ど ろ
ろ ど ろ ろ 13 ど ど
14 ど あ 18 あ 0b 53

となる。もうちょっと工夫の余地がある。

難しいポイント

変数や関数呼び出しを使うためにはバイトコード部ではなく定数テーブルに名前の文字列を積んで、そのインデックスを指定してLOAD命令を発行する必要がある。短歌以外の部分に意味のある文字列を色々書き込むと「31文字で完結」という短歌の良さが失われるので避けたい。だから変数も関数も使ってはいけない。

定数を使うのはもちろん定数テーブルから読み込む必要がある。上記と同様にそこに書き込むことはやりたくない。ただ、実は空の関数でも定数テーブルには「None」が積まれている。唯一使える定数だ。

定数ロードの命令は3バイト消費するので不用意に使うとあっという間に31文字を使い切る。というわけで冒頭部に1回だけ使う。冒頭の64 00 00が「LOAD_CONST 0」である。

NoneにUNARY_NOT(0c)を使うことでboolへの変換が行われて、結果がTrueになる。Trueに対してBINARY_ADD(17)などの算術演算を使うことでintegerへの変換(True→1)が行われる。これによって数値を作るための種を生み出している。

PythonVMはスタックマシン。「04(ど)」はスタック先頭を複製する命令DUP_TOP。「03(ろ)」はスタック頭3個を回転する命令ROT_THREE。「17(あ)」がスタックトップ2つを加算して戻す命令BINARY_ADD。

バイトコードの実行方法

空の関数だけがおかれたモジュールをimportして、コンパイルされたpycファイルを開き、バイトコード部分を書き換えて別の名前で書き出す。その後、それをimportして実行。

https://github.com/nishio/tanka/blob/master/tanka_src.py

BPT(Byte per Tanka)についての考察

Python Bytecode短歌においてのBPT上限について考察する

上5バイトは上記のコードと同じ、次の7バイトで以下のようにすることで残りがNOPでも618文字出力できる。

        ADD, # 2
        DUP, # 2 2
        ADD, # 4
        DUP, # 4 4
        POW, # 256
        DUP, # 256 256
        POW, # 256^256 (about 616 digits)