わらばんし仄聞記

南の国で引きこもってるWeb屋さん

簡易brainf*ckコンパイラを作る part3

ここまでで触れていたもので、準備は整った。本題に戻って、brainf*ckのコードをプログラムに読ませ、そのコードの実行ファイルを生成する。
具体的にどうするかを攫っておくと、

  1. pythonでJIT(64bit版Linux環境) part3を元にして、読み込んだコードをネイティブコードに変換する
  2. 簡易brainf*ckコンパイラを作る part2で生成していたELFのメインコード部分を上で作ったコードで置き換える
  3. これらに応じて各ヘッダ部の値を整える

を行う事になる。

目標

brainf*ckのコードをプログラムで読み込み、そのコードについての実行可能ファイルを生成する

コード

コードはpythonで記述してあります。
とりあえず動作することを目的に書いてあるので、最適化やコードの汚さは目をつぶっておいてください・・・

基本的にはJITでやってた頃のコードを使い回した形になっています。
異なる点としては、JITと異なりその場で実行する必要が無いのでその辺りが省かれているところが。

処理の流れとしては、248行目~で実行時に引数として渡されたbrainf*ckコードを読み込み、253行目でそのコードをネイティブコードへ変換したものの配列を取得。255行目でそのネイティブコードを包含するELF形式のバイナリ値を持った配列を作成している。
以降はそれをファイルに出力しているが、ここはstruct.pack()を使ってファイルに書いてもよかったが、JITの時にデバッグ用として出力していたいのがあったのでそれをそのまま利用。

残りの部分は地道にバイナリ値を設定しているだけなので特に注釈を入れるような所は無いかと。

注意としては、translate関数内の処理について、JITの時はpythonコードの実行処理内にbrainf*ckのコードが関数として実行されるような状況であったため、最初にいくつかのレジスタをpushし、最後にはpopした上でret命令で終わるようになっている。
今回の場合は動作させる処理はbrainf*ckのコード以外には無く、同時に、処理を戻すべき場所も無いため、コードの最初にpushは不要かつ終了時はexitシステムコールで終わらせる必要がある。

実行

hello world

$ cat hello.bf
>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++++>-]<.>+++++++++++[<+++++>-]<.>++++++++[<+++>-]<.+++.------.--------.[-]>++++++++[<++++>-]<+.[-]++++++++++.

$ python bf_compiler.py hello.bf
$ ./elf.out
Hello World!

echo

$ cat echo.bf
>>++++++++[->++++++++<]>>>>+++++++++[->++++++++++<]>[<<,[->+<<+<<+>>>]<<<[->>>+<<<]>>>>>[->+>>+<<<]>[<<[->+>>+<<<]>>>[-<<<+>>>]<<[[-]<->]>-]>>[-<<<+>>>]<<<<<<<[-<+<<+>>>]<[>>[-<+<<+>>>]<<<[->>>+<<<]>>[[-]>-<]<-]<<[->>>+<<<]>>>>><[[-]>++++++++++++++++++++++++++++++++>[[-]<-------------------------------->]<<]>>[-]<.>>]

$ python bf_compiler.py echo.bf
$ ./elf.out
A
A

conclusion

この他、いくつかbrainf*ckのコードを余所様より参照して試してみたけど、無事に動作してくれている模様。
非常に簡易なものではあるが、brainf*ckのコードから実行可能ファイルの生成、つまりはbrainf*ckコンパイラを作ることができた。

元々これを作り始めたのは丁度一年ほど前、コンパイラ実装会という勉強会に参加させていただいた事に端を発します。あれこれ寄り道してたせいもあって一年もかかってしまいましたが、一つの里程標まではなんとか辿り着けました。
例えば実行ファイルの生成一つとってもライブラリとのリンク等々、他にも課題が山積ではあるが、まずは今後のスタート地点までは来れた事が嬉しい。