2020年度のRuby Association開発補助金(RA Grant)によるブーストを得て、mini mruby compilerを開発しています。 簡単に言うと、ワンチップマイコンでも動作する省リソースなmrubyコンパイラです。
先日、その中間報告を提出しました。 審査を通過したのかどうか連絡がない(そもそも審査があるのかもよくわからない)のですが、却下するとも言われていないので大丈夫なのかなと思います。
進捗
大ざっぱに分類すると、条件文のコンパイル、ブロック文のコンパイルと入れ子状IREPのVMコードジェネレートができるようになりました。
コンパイラを書いてみると、改めて初心に戻れる(いやべつに上級者でもなんでもないですが)気がしたので、本稿ではそのへんの学びを書き留めます。
条件文
条件文の代表例は以下のようなものです:
if a == b
hoge
else
fuga
end
コンパイル後のVMコードでは、条件を判定する部分 a == b
を実行して、その結果の真偽をもとにVMコードの実行位置をジャンプ命令によって変更します。
Rubyのような高級言語でも、内部ではGOTOキャンペーンをやっているという訳です。
コンパイラを自作して改めて理解できたことがあります。 例えば、下の2つのコードのコンパイル結果は、まったく同一です(ん?最適化?なんですかそれ):
# (1)
if a == b
true
end
# (2)
if a == b
true
else
nil
end
(1)のコードをコンパイルしても、コードジェネレータは条件が偽だったときのために nil
をセットする命令を生成します。
結果的に、(1)のように書いても(2)を書いたのと同じVMコードにコンパイルされます。
これはRubyの基本原則である「ほとんどの式は値を返す」(合ってる?)が実践されているからです。
さらに、以下のコードもまったく同じコンパイル結果になります。
# (3)
true if a == b
IFモディファイアとでも呼ぶことのできるこのような文法は、パーサによって(1)と同じ中間表現に変換されるので、コードジェネレータから見ると(1)と(3)の間には差異がありません。
以上を踏まえると、Rubyを書いている人なら一度はやった(?)であろう以下のタイプのミス(というか、意図しない結果を得るメソッド呼び出し)もよく理解できます:
# (4)
def my_method(a, b)
a if a > b
end
Rubyのもうひとつの基本原則「メソッドに明示的にreturnを書かない場合は、最後に評価された値がメソッドの返り値になる」(ですっけ?)とあわせることで、(4)は(5)と等価であることがわかります:
# (5)
def my_method(a, b)
if a > b
return a
else
return nil
end
end
しまった! nilが返ってきてたー! ってなったことあるでしょ?(正直に言いなさい)
ブロック文と入れ子のIREP
ブロック文はこういうやつですね:
# (6)
[0, 1, 2].each do |i|
puts i
end
ブロックの内側(ここで言うと puts i
の部分)は変数のスコープが変わるので、ランタイムの実行環境もスコープの遷移が必要です。
そのため、ブロックが登場する度に新たなIREPが「起こされます」。
IREPとは「VM命令表現の連なり」くらいの意味です。 そしてIREPは、親子関係のような入れ子状になるようコードジェネレートされなければなりません。また、子同士には順序があり、兄弟のような関係になります。
ここが、いままでで最も技術的に難しいところでした。 Rubyで書くなら簡単でしょうが、C言語だとちょっとしたことにも頭を使いますね。
ところで do なんとか end
はぜんぶブロック文であるかと言うとそうでもなくて、以下のRubyはブロック(子IREP)を生成しません:
# (7)
a = 0
while true do
put a
break if a > 10
a += 1
end
WHILE文はじつは、IF文の発展形です。条件判定と実行位置ジャンプの組み合わせで実現されます。
するとつぎに問題になるのが、breakなどのフロー制御です。 WHILE文の中のbreakはジャンプ命令((7)の場合だと、endの直後へジャンプする)へとコンパイルされ、ブロック内のbreakはOP_BREAKのような専用のVM命令(一段上のIREPへ制御を戻す)へとコンパイルされます。
コンパイルされますって簡単に書いていますが、そういうふうにコンパイルするようなコンパイラをつくっております、ということなんです。
今後の予定
RA Grantのスコープに入っている機能開発の、大ざっぱなTODOリスト:
- defキーワードからのメソッド定義
- classキーワードからのクラス定義
これらも入れ子状のIREPを生成する文法ですね。それと、
- mrubyのバージョン3で変更された(未リリースなのであくまで変更予定ですが)VMコードへの対応
あとはメモリリークの修正、大量の実装漏れとバグ修正があると予想しています。
実装漏れが辛そうですけど、実装しすぎてコードサイズが大きくなったらワンチップマイコンに載せられなくなるので、、、という逃げ道も用意してあります(心理的安全性の確保)。
RAM消費量の削減にも取り組みたいと思います(少し効きそうなアイデアはある)。
3月の最終報告まであと2か月。。。
がんばるぞ💪