前回( http://d.hatena.ne.jp/mjt/20170501/p1 )はVMの基本的なコンセプトを考えた。今回はとにかくVMコンパイラを作る上で最初に必要になる、レジスタの構成と手続きの呼び出しプロトコルを考える。

SECDVの レジスタ 構成は、SECDR Scheme のR レジスタ をV(Values) レジスタ に置き換えたものになっている。機能的には殆ど同じものになるが、多値を受け取るためアクセス時にindexを指定することになる。 Code レジスタ には定数ベクタを保持するConstants レジスタ も含めた。SECDR Scheme にせよnmoshにせよ、一般的な Scheme VM は VM のためのマシンコード自体も Scheme オブジェクトとしていることが多くマシンコードと Scheme オブジェクトを"交ぜ書き"できるようになっている。が、SECDVでは諸般の事情で マシンコードをfixnumのベクタとする必要がある ため、定数となる Scheme オブジェクトはindexでアクセスさせる必要がある。 Stack レジスタ は後述の理由により最早スタックではなく、固定長のベクタを指すことになる。手続きのParameterなのでP レジスタ か何かに改名した方が良い気もする。 微妙な追加は Link レジスタ で、多値を直接扱うSECDVならではの理由でSECDRから追加されている。

Linkレジスタによる固定長引数呼び出しの最適化

VMが直接多値を扱うため、SECDR Schemeでは1種類しか存在しなかった "Stackへの引数の積み方" "呼び出された手続きがStackからEnvironmentに値を移す方法" "戻値レジスタに値を返す方法" の3つが複数種類存在することになる。

もっとエレガントな解が有るような気がしてならないが。。今回はこれらをLinkレジスタを追加して区別できるようにしてみた。



例えば、手続き proc を "(proc a b c d)"のように4引数で呼び出す場合は、Fixedプロトコルとなる。この場合、Sレジスタは4要素のベクタを指すことになり、Linkレジスタには"Fixed"をセットする。

しかし、同じ手続きを "(apply proc a b (list c d))"のように apply 手続き経由で呼び出す場合は、Multiプロトコルとなる。この場合、Sレジスタは3要素のベクタを指し、最終要素には"(list c d)"の部分が入ることになる。



このどちらの方法で呼び出しても、構築されるEnvironmentは同一のものになる。...簡単なSchemeコードを書いて確かめられる。



Environmentの構築方法も、手続き proc がどのように書かれているかで異なる。proc = "(lambda (a b c d) ...)"であれば、Environmentの0段目は4要素のベクタを指すことになるし、proc = "(lambda (a b . rest) ...)"であれば、Environmentは3要素のベクタを指すことになる。



重要なポイントは、proc手続きがどのように書かれているかは呼び出し元からは判別不能である点で、1つのマシンコード列で両方に対応できるようにするには、呼び出し自体にステートを設け、動的に呼び出しプロトコル変換を行えるようにするしか多分方法が無い。

このようなややこしいルールを設けるのは、Schemeプログラムの大部分を占める固定長引数の手続き呼び出しを最適化するため。Listを呼び出しの度に生成すると、手続き呼び出しで確実に複数個のconsセルを必要とすることになる。SECDR SchemeではSレジスタは常にlistを指しているため、呼び出しプロトコルもSECDVで言うところのMultiプロトコルしか存在しない。