SPVMロードマップ - ついにこれからJITの実装に入れそう

開発を始めてからもう少しで1年を立とうかという「ほぼPerl風静的言語SPVM」がJITの実装に入れそうだ。

いろんな言語にJITの実装があるけれど、Perlはないなぁと、感じているあなた。PerlJITやります!

正確にいうと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と全く同じ形式の呼び出しを実装できた。