ディクショナリー


リターンスタックなどもそうであるが、ディクショナリー(辞書)は、言語環境が自動的に保守するメカニズムであって、コードやデータがコンパイルされるときに、暗黙のうちに利用されるものである。けれども、そういう機構もプログラマーが操作できるようにしてしまうのがforthの流儀であるともいえる。

Forth走行中、ディクショナリーはメモリ中にロードされている。そこには、相互にリンクされた既定義ワードのデータとコードが格納されている。インタープリターもコンパイラーも、そこに収められている。

古典的なforthでは、機械命令を含む実行コードも、数値データを格納する変数用フィールドも、全て混在する形で一体としてのディクショナリー領域に格納されていた。かつては、通常の実行可能ファイルもそのような状態であったので、それが自然であった。

その後、CPUが機械命令実行の効率化(特にキャッシュ関連)を目的として、基本的には事後に変更されることのないデータである機械命令を格納する領域と、動作中に変更される可能性のある変数域とを分離することを推奨するようになった。実際、コードとデータを混在させると、実行速度上に非常に大きなペナルティーが生ずるCPUもあるようである。Forth環境もネイティブコードタイプが増えるにしたがって、ディクショナリー内でコードとデータを分断して配置するものも増えてきた。その結果として、かつての一体型のディクショナリを想定した伝統的なforthワードは、その意味を変えざるを得なくなった。

HERE


HEREは、元来は、その実行時点でのディクショナリーの空き領域の開始アドレスをスタックに残すワードである。
HERE  ( -- addr )
しかし、上に述べたように、コードとデータを分離したディクショナリーに対応して、現在では、空きデータ領域の先頭アドレスを返すこととなっている。
Hereが指しているデータディクショナリーの空き領域を一時的なバッファーとして利用することもできる。

DP CDP

MopsでもHEREはディクショナリーのデータ領域のアドレスを返すワードとして定義されているが、内部的には、DPというValue変数で管理されている(Data Pointerの略であろう)。したがって、DPはHEREと同義である。
DP   ( -- addr ) \ データ域アドレス
ただし、DPは変数であるから値を動かすことができる(もっとも、直接動かすのはあまりよいコーディング方法ではないが)。HEREは変数ではないので、外から値を変更することはできない。

Mopsでは、ディクショナリーの空きコード領域の先頭アドレスを管理するValue変数がある。それがCDPである(Code Pointerの略と思われる)。
CDP  ( -- addr ) \ コード領域アドレス

UNUSED


UNUSED (Un-used(使われていない))は、ディクショナリーのデータ領域のまだ残っている部分の大きさをアドレス単位(普通はバイト)数で返す。つまり、HEREの後、データのために領域確保してある部分である。
UNUSED  ( -- u )
これによって、どれくらいスペースに余裕があるかがわかる。Mopsでは初期状態で1MB程度である。

C, , ALIGN


スタック上から数値データをディクショナリーのデータ域に直接格納するワードもある。

C,は、1バイト数をスタックから取って、HEREの場所に格納し、HEREを1進める。
C,  ( c -- )

1文字ワードである ,(comma)は、1セル数値をスタックから取って、HEREの場所に格納し、HEREを1セルバイト分進める。
,  ( x -- )
しかし、このワードは、データ域がキチンとアラインメントされているかどうかは確認しない。
アラインメントの問題とは、開始アドレス値がキッチリと割り切れているかどうかということである。1セルが1バイトより大きい場合、1セル幅の数値データが、1セルバイト数でキッチリと割り切れないアドレス値の位置(1セルが4バイトなら、アドレス番号が4で割り切れない場合、ということ)に格納されていると、通常、機械の仕組みのためにデータをやり取りする時間にペナルティーがある(例外を投げるものもあるようである)。そこで、格納前にアラインメントをキッチリしておくためのワードが、ALIGNである。
ALIGN ( -- )
このワードが実行されると、データ域の開始アドレス(HERE)の値を進めて、アドレス数値が1セルで割り切れる数値になるように調整する。見た目には何も起こらない。

FALIGN F,


浮動小数点数を辞書に格納する場合に、データディクショナリの切れ目を合わせるのがFALIGNである。
FALIGN ( -- )
当のシステムでの標準浮動小数点数のバイト幅で整理される。
浮動小数点数の値をデータディクショナリに格納するのはF,である。
F, ( F: r -- )
基本は整数と同じである。

実際上の使用法


ディクショナリーにデータを保存したとしても、その位置が自動的にどこかに記憶されるわけではない。したがって、以上のようなディクショナリー格納ワードを用いる場合、必要なら、実行前に、その位置をどこかに保存しておかなければならない。このアドレス値は、ディクショナリーを保存しても再起動後には無効になってしまうのが一般的である(多くのforthプログラマは、なぜかこれを問題と考えない)。Mopsではアドレス保存用のデータクラスが準備されている。このような問題があるので、上のようなディクショナリー格納用ワードは、通常は、何かワードをコンパイルするためのワードを作るとか、低レベルなことをする場合に用いられるか、アルゴリズム中の臨時使用バッファーとして、ヒープの代わりにディクショナリーの空き領域を使う、などという使い方がほとんどである。


さて以上が標準forthのワード規格だが、Mopsでは歴史的事情によって、もっと細かく分かれている。

W,

W,は命名慣行からすぐに分ると思うが、2バイト数をディクショナリーのデータ領域に格納する。DPは2進められる。
W,  ( w -- )
また、iMopsでは1セルは8バイトであるから、ALIGNは8バイトアラインメントになるが、, (コンマ)は4バイト数としての格納となっている。8バイト数として辞書に格納するためにDATAZ,が定義されている。
dataZ,  ( x -- )
なお、iMopsでデータポインタを4バイトアラインメントとするワードとしてDATA_4ALIGNがある。
data_4align  ( -- )

CODE_ALIGN CODE_4ALIGN CODEC, CODEW, CODE, CODEZ,

これまでの辞書への書き込みは、データ領域に関するものであった。Mopsでは前に述べたように、データとコードは分離されているので、コードディクショナリーに関しては別系統のワードが必要である。上の見出しにあげたものがそれである。適宜「CODE」部分を取り除けば、それぞれのデータ領域用ワードに対応したものになる(CODEZ,だけはDATAZ,と対応すると考える。)。

これらは、まさにデータを直接記録するのであるが、古典的forthでは(現在でも間接スレッディングを採用しているforthなら)、ディクショナリへの書き込みが「コンパイル」と観念されており、特に実行ワードのXT(エグゼキュショントークン)を書き込むことは、言葉通りコンパイルの意味を持ち、そこにワードの呼び出しコードがコンパイルされることになっていた。しかし、現在では、コードディクショナリへの上のワード系列による書き込みだけでは、通常の「コンパイル」の意味は持たず、書き込まれるデータがワードのXTであったとしても、ワードの呼び出しがコンパイルされることは、標準としては、ない。XTを文字通りコンパイルするには、別にCOMPILE,というワードが定義されている。これについては、別のページで説明する。

MARKER FORGET


これらは、上記のものとは性格が異なるが、辞書を操作するワードとして、便宜上、ここで説明しておく。

MARKERは、宣言形式でワードを定義するワードである。したがって、実行時には、後方に入力としてそこで定義するワード名を必要とする。
MARKER  ( "<spaces>name"  -- )
そこで、例えば、
MARKER Mark1
とすれば、Mark1というワードが定義される。その後、他にもワードを定義するなどしてディクショナリーに格納されたとする。そこで、ワードMark1を実行すれば、ディクショナリー状態は、MARKERを用いてMark1が定義された時点の直前の状態に復帰する。つまり、MARKER以降のディクショナリー領域にある部分の内容を廃棄してしまうのである。したがって、再び新たな定義をコンパイルすれば、コードはMARKERのあった位置から埋め始めるわけである。

FORGETは任意のワードについて、そのワード以後のディクショナリーを廃棄する。つまり、FORGETは実行時にディクショナリを一部廃棄してしまうのであり、その復帰点を示す既定義ワードを、後方に入力として取る。
FORGET  ( "<spaces>name" -- )
しかしながら、FORGETはobsoleted(旧式)であるとされている。つまり、MARKERでマーキングワードを定義する方法に取って代わられたわけである。Mopsでは、PowerMopsまでは定義されているがiMopsでは定義されていない。
ディクショナリーが一枚岩であるような実装ならばFORGETでもよいが、コードとデータを区分する方式では、一般の実行可能ワードは、その定義の時点でのディクショナリのデータ領域の開始アドレスのデータを保有しているとは限らず、単に一般ワードを指定するだけでは、ディクショナリーを適切に復帰させることは難しいのである。それが、おそらく、MARKERが導入された理由であろう。


次は、



最終更新:2014年02月10日 13:15