SPVMロードマップ - ついにこれからJITの実装に入れそう
開発を始めてからもう少しで1年を立とうかという「ほぼPerl風静的言語SPVM」がJITの実装に入れそうだ。
いろんな言語にJITの実装があるけれど、Perlはないなぁと、感じているあなた。PerlでJITやります!
正確にいうとPerlではないけれど、ほぼPerl文法で記述できるSPVMの関数をJITでコンパイルして、それを、直接Perlから呼び出せるようになるので、まぁPerlといってもよい。
たとえば、SPVMで書いたPointクラスをPerlから利用する場合は、次のような感じになる。
use SPVM 'Point'; my $point = SPVM::Point::new(); $point->set_x(3); my $x = $point->get_x(3);
SPVMのほうはこんな感じ。
package Point { # フィールド has x : int; # アクセッサ set x; get x; # コンストラクタ sub new () : Point { return new Point; } }
JITの実装方法
SPVMのJITは、アセンブラコードをまったく書かない方法を採用する。x86とかARMとか、CPUに依存したコードは使わない。
SPVMは、完全な静的な型と関数呼び出しを持つので、それをC言語のソースコードに落とし込んで、GCCを使って、実行時にコンパイルするということで実現しようとしている。
SPVMは動的な部分を一切持たないので、C言語ソースコードに落とし込むことができる。そうすると、GCCを使って、マシンコードにコンパイルすることができる。これを実行時に行う。
Linuxでも、Macでも、Windowsでも動く。x86でもx86_64でも、ARMでも動く。GCC 4.1.2以上で動く。これはCent OS 5に積まれたGCCだ。とても、広い環境で動くだろうと思う。
コンパイル時間は、遅いと思う。でも、ほぼPerlで書いて、速度実現の部分だけをSPVMで書けば、全体としてみれば、それほどかからないのではないかという気もする。
ここ1ヶ月でやっていたこと
ここ一ヶ月でやっていた作業を書き出してみる
スタック型VMからレジスタ型VMへの書き換え
ここ1ヶ月の一番大変だった作業は、スタック型VMからレジスタ型VMへ書き換えることだ。この二つのVMの最も大きな違いはなんだろうか? なぜ書き換える必要がでたのだろうか。
SPVMはJavaバーチャルマシンを参考にして実装を始めたので、一番最初は、スタック型VMだったのだ。スタック型VMは、値を計算するのに、スタックと呼ばれる領域に、値を積む。スタックとはすなわち、動的な領域である。
動的な領域であるということは、単純なローカル変数ではないということである。単純なローカル変数ではないということは、レジスタ割り当てアルゴリズムを適用できないということである。
SPVMは、最終的には、パフォーマンスの最適化として、レジスタ割り当てアルゴリズムを、GCCにお任せしようとしている。とすれば、スタック型VMでは、最適化ができないということだ。だから、レジスタ型VMにしないといけない。
レジスタ型VMとは、ものすごく簡単にいえば、ローカル変数につねに値を保存するという感覚のVMだ。変数1と変数2の値を足して、変数3に代入するという感じ。
こういうVMにしておけば、JITをするときに、そのままC言語のローカル変数に対応させることができるね。C言語の構文とピッタリと1対1に対応することができて、ローカル変数を使っているから、GCCが、レジスタ割り当て最適化をしてくれるんよ。
メモリアクセスは遅く、レジスタアクセスは、めちゃ速い。だから、数値計算しているときの値は、レジスタにいてもらえるとありがたいのねー。だから、このレジスタ割り当てアルゴリズムを、使いたい。だから、レジスタ型VMが必要というわけ。
Perl風関数呼び出しに近づけた
もう一つやっていた作業が、関数呼び出しを、もっとPerl風に近づけるということ。作業を簡単にするために、関数呼び出しを完全修飾名だけにしていたんだけど、これを、Perlと同じにした。
# 標準関数 std::print("AAA");
これが、printだけで呼び出せるようになった。
print("AAA");
同じパッケージの中にある場合も、完全修飾をする必要がない。パッケージ変数も同様。同名の標準関数を呼び出したいときはCOREパッケージ名を付けることができる。ひとつの制約として、パッケージ変数は大文字で始まることとした。レキシカル変数は小文字。呼び出すときに、名前だけで区別できるように。
package Point { our $BAZ : int; sub foo() : int { return 1; } sub bar : int { # foo() で呼び出せる foo(); # $BAZで呼び出せる $BAZ = 1; # COREパッケージ CORE::print("AAA"); } }
メソッド呼び出しもできる。
$point->set_x(3);
これで、関数呼び出し、メソッド呼び出しの形式、絶対修飾名ではないときの呼び出し、COREパッケージと、Perlと全く同じ形式の呼び出しを実装できた。