iMopsのインタープリタの機構は、PowerMopsのそれを模倣している。INTERPRETという内部ワードが中心となっている。

実行モード


実行モードでインタープリトする場合、INTERPRETは、読み込まれたワードを辞書内検索で探し出し、そのワードのxt(エグゼキューション トークンの略号。forthでは実行可能ワードを識別するタグのようなものを、このように呼ぶ。)をパラメターとして、もう一つの内部ワードEX_GENを呼び出す。EX_GENの名は、もともとあったワード名から踏襲されたものだが、どうも、Execute_Generally(一般実行)の意味らしい。

このEX_GENの内容として、素朴に考えてまず思いつくのはxtからコードアドレスを取ってcall(呼び出し)するやり方だろう。
ここでいうcallはマシン語のcallのことで、現在実行されているコードの終わり部分のアドレスをスタックに積んで、特定のコードアドレス(対応するマシンコードが格納されているメモリー内の場所)に制御を飛ばす。そして、ret(リターン)のマシンコードに出会ったときに、スタック上のリターンアドレスを使って制御を戻す、という動作になる。

古典的forthはここでcallではなくてjmp(jump:ジャンプのマシン語である)を使う。
まずリターンスタックに実行されるワードが自分で現在のコードアドレスを積み、
それからマシン語のjmpで目的ワードのコードアドレスに制御を飛ばす。
そして、ワードの実行が終わった後は、当のワードがリターンスタックを用いて自分で制御を引き戻す。

とはいうものの、MopsではワードアドレスをワードEX_GENが直接callすることはない。というのも、変数等もワードの一つであるが、変数はcallに対応した実行コードは持っていない。そのままだと、インタープリターがワードの属性をチェックして具体的な実行については条件分岐しないといけない。けれども、それもしていない。

実は、EX_GENは、まずコードバッファ内に該当ワードの呼び出しコードをコンパイルするのである。そしてコンパイル完了した後、そのバッファの該当個所に実行をジャンプする。バッファというのは要するにメモリーの空き領域である。適当にズラしながら巡回的に上書きして利用する。バッファを使うことを別とすれば、例えばword1の実行は、

:noname   word1  ;  EXECUTE

というコードの実行と、ほぼ同値な形で実現されている。そうすることで、ConstantとかVariable変数、Value変数も、普通のワードも同じ仕組みで実行できるようになる。この実行は一語一語行う。毎回コードをコンパイルするので実行速度に関しては不利である。とはいえ、インタープリターといっても、もう定義されたワードは全部マシン語になっているので、コードの読み込みコンパイルなども十分速いと思う。実際、約150キロバイト辞書を消費するiMopsのカーネル拡張コードのコンパイルぐらいでは瞬時に終わり、ロードメッセージの印字にほとんどの時間を喰われている感じである。

「ほぼ」同値、といったのは、完全に同じではないからである。実は同じでも全然かまわなかったのだが、
ここでは少し(いい気になって)効率化している。実行が終わった後、普通のEXECUTEだと、その直後部分に実行を戻さないといけない。
けれども、EX_GENに戻ってきても、もうやることはなく、あとはリターンだけである。
そのようなわけで、コードバッファーには、ジャンプで飛び移っている。すると、戻るときには一つ飛び越えて、
INTERPRETの中のEX_GENを呼び出した直後の部分に、実行が返ってくるからである。

この方法の利点は、もちろん、条件分岐を書かなくて済むこと。
例えば、ワードの新しい類型の導入が必要になっても、インタープリタ部分は書き換える必要がないのである。

古典的な間接スレッディングforthでも、ワード類型によらず同じ手順で解釈実行する。このような構成はモジュラリティーを高める。
個人的にはコードの局所性(locality)と呼んだりしているが、この特性は、表面的な観察としての'単純さ(simplicity)'よりも
重要なのではないかと思う。

この方法の弱点は、インタープリテーションのためにコードジェネレーター(コンパイラ)を必要とすることである。例えば、単独実行可能アプリケーション内で、特定のイベントハンドリングにおいてEVALUATEなどのインタープリテーションを実行することは、原則禁止である。単独実行アプリケーションでは、実行されるワードはすべて既にコンパイルされていると想定されている。
例外として、iMopsではコードジェネレーター込みで単独実行アプリケーションをビルドする方法を追加したので、
メモリーロスや実行速度が問題ではないという場合には、その方法を使えば、EVALUATEが実行時に可能になり、
アプリケーション内で新しいコードをロードして新ワードを定義して呼び出したり、後にそれを削除して新たにロードしたり、といった
自己増減型の独立アプリケーションが可能である。しかし、推奨はしない。


コンパイルモード


コンパイルモードでは、通常のワードはその呼び出しがコンパイルされ、IMMEDIATE指定されたワードだけ実行される、という決まりになっている。コンパイルモードで作動するのは、通常は、コロン : で始まりセミコロン ; で終わる領域、つまり、ワードを定義している部分である。

iMopsでは、ワードの呼び出しといっても、全部が「呼び出し」といえるような動作をするわけではない。まず、ワードのxtデータから、そのワードの属性にしたがって分類する。基本的には、変数と実行ワードと特殊ワードの三つに分かれる。文字通り呼び出しをコンパイルするのは、普通の実行ワードだけである。変数では、変数のアドレスやデータ域からデータを取り出してスタックに置くマシンコードをそのまま書き込む。特殊ワードというのは、四則演算や比較、スタック操作など、基本的なワード群で、これらはインラインでコンパイルされる。

普通の実行ワードの呼び出しには、Callインストラクションが用いられる。上に示唆したようにxtを通じてマシンコード域のアドレスが取れるので、それを用いて相対アドレスでCallするのである。ここでのパラメター等のコンパイルの仕組みは、別のページで説明する。
ちなみに、CやObjective-Cのライブラリ関数も、ここでの観点からいえば、普通の実行ワードである。実際、外部呼び出しは、普通の実行ワードをまず定義しており、そのワードの内容がライブラリにダイナミックリンクするマシンコードになっている。この辺りも、別のページで説明する。

コンパイルモードでも、IMMEDIATE指定されたワードは実行される。IMMEDIATEは、ワードのデータに特定のフラグを立てる古典的な方法を使っている。このフラグは、辞書内検索ワードがチェックする。そして、IMMEDIATEであることが分れば直ちに実行しなければならないわけだが、このときには、xt EXECUTEの仕組みを用いている。IMMEDIATE指定されるワードは実行できるxtを持っているというのが前提なので、これで用は足りるはずである。(などとエラそうに言っているが、単にPowerMopsの機構を模倣しただけである。)

既定義ワードではない場合


辞書検索の結果、発見できなかったものは、数値(リテラル)であるかどうかをチェックする。
整数であれば、文字列を実際の数字に直し、スタックに積み込む。これにはFoundationフレームワーク(Mac OS XのCocoa/NeXTStepの基幹関数群ライブラリ)のユーティリティー関数もあったが、整数の場合簡単なので、自分でアルゴリズムを書いた。
整数ではない場合には、小数かどうかチェックする。浮動小数点数の場合、いくつか書式があって、全部に対応するのが面倒なので、ユーティリティー関数を使った。

どれにも当てはまらなかったときには、その場で処理をabort(中断)してエラーを表示する。古典的に、だいたいこういう仕様になっている。エラー表示には、最後に呼び出されたワードの名前が表示されるようになっているので、未定義ワードの場合、大抵、数字を解釈するワード ( TRY_NUMBER ) の名前が表示されることになる。これはあまり役に立たないメッセージではあるが。



最終更新:2013年07月26日 12:13