SECDV Schemeを考える - レジスタ構成と呼び出しプロトコル

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

SECDV VMレジスタ


SECDVのレジスタ構成は、SECDR SchemeのRレジスタをV(Values)レジスタに置き換えたものになっている。機能的には殆ど同じものになるが、多値を受け取るためアクセス時にindexを指定することになる。
Codeレジスタには定数ベクタを保持するConstantsレジスタも含めた。SECDR Schemeにせよnmoshにせよ、一般的なScheme VMVMのためのマシンコード自体も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プロトコルしか存在しない。