というわけで、前回( http://d.hatena.ne.jp/mjt/20170321/p1 )見たSECDRマシンをベースに、多値を扱えるVM(SECDVマシン?)を考えていく。

多値を直接的に扱うことで、メモリ効率が改善できるのではないかと考えている。SECDRマシンでは環境(Environment)を表現するためにリストを使っており、フレーム内のformal、つまり手続きの引数1つごとにCONSセルを1つ消費することになっている。通常のシチュエーションではSchemeの手続きの引数は2つ以上あるため、ちょっともったいない気がする。

手続きが2つ以上の引数を取ることを前提にするということは、手続きが多値を取ることが多いということになる。常識的なシチュエーションでは手続きの引数は固定長個であるため、環境フレームをlistの替わりにvectorで表現することに近い。



listではなくvectorを使うという以外は、SECDRマシンと環境モデルに大きな違いはないが、可変長引数の手続きとかapplyどうすんのかという問題が残る。

基本的には、SECDRマシンのR(Result)レジスタを複数の値が格納できるように拡張し、V(Values)レジスタとする。実際には、Vレジスタに複数値を置けるようにするだけでは手間の割に効果が薄そうなので、"Vレジスタファイル"としてレジスタをスタック状にして扱えるように考える。



Vレジスタは、手続きが戻した値を指す。手続きが多値を返した場合には、それが8値まで(図だと4だけど)であればVレジスタファイルに値を置く。Vレジスタファイルの"横幅"は固定長であるため、更に有効長フィールドも必要になる。(values手続きで返した多値はどうせcall-with-valuesプリミティブかSRFI-8 receive 構文 https://srfi.schemers.org/srfi-8/srfi-8.html によってbindされるため、直後にフレームに取り込まれると考えて良い。)

Vレジスタファイルは手続きへの入力を示すSレジスタからも指されることがある。図は"手続き proc を呼ぼうとして、引数 a を正に積もうとしているところ"で、Sレジスタの指す、Vレジスタ先頭には a が格納されることになる。

実際のVレジスタの消費段数はコンパイル時には判らないため、VMコンパイラは、Vレジスタファイルは無限段あるものと仮定してコンパイルする。実際のVM実装では、SPARCのレジスタWindowのように8値x8段くらいのキャッシュを持ち、溢れたものからVectorにコピーして置き換えていくことになる。関数フレームのエントリ数が動的に拡張されることは無い(= 一度コンパイルしたクロージャにローカル変数が追加されることは無い)ので、ここで必要なフレームのエントリ数はコンパイル時に決定できる。

このため、環境 E からVレジスタファイルを指す場合は1段間接参照されることになる。VレジスタファイルからVectorへの巻き直しが発生した場合は、環境からVレジスタファイルを指しているポインタの方を、巻き直ししたVectorを指すように書き換えることになる。

ちょっと悩みどころなのは8値x8段のように1段あたりの段数を固定長にするかどうかという点で、常識的に考えて1段あたりの段数は可変にした方がメモリ効率は良い。ただ、制御ロジックを複雑にする分のリターンが有るかどうかは何とも言えない。この辺は実際のアプリケーションを使って検証したいところ。

... もちろん真面目に書いたスタックベース/レジスタベースVMの方がずっと安くて高速だが、真面目に書かないといけないのでそこはサボりたい。ついでに言うとSECDRと比べてSECDVが複雑になった分のリターンが有るのかどうかも現状よくわかっていない。