プリミティブは移植可能な言語(portable language)で実装されているため、 最早プリミティブの数を最小限に抑える必要はありません。 逆に、 多くのプリミティブを持つことには速度という利点があります。 プリミティブでのエラーの数を減らし、 プリミティブのプログラミングを容易にするために、 プリミティブ・ジェネレーター (prims2x.fs 別名 Vmgen see Introduction in Vmgen)というツールを提供しており、 それは、 スタック効果の表記からプリミティブの C言語コードの大部分(場合によってはすべて)を自動的に生成します。 プリミティブのソースの形式は以下のとおりです:
Forth-name ( stack-effect ) category [pronounce] [""
glossary entry""
] C code [:
Forth code]
角括弧(brackets)内の項目はオプションです。
カテゴリ(category)と発音(pronounce)と用語集(glossary)のフィールドはドキュメントを生成するためにあり、 Forth
コード(Forth code)は GNU C を持たないマシンでの手動実装のためにあります。 たとえば、 プリミティブ +
のソースは以下のとおりです:
+ ( n1 n2 -- n ) core plus n = n1+n2;
これは仕様のように見えますが、 実際には n = n1+n2
は C言語のコードです。 私たちのプリミティブ生成ツールは、
スタック効果の表記から多くの情報を抽出します38: それらは、 スタックからポップされた項目とスタックにプッシュされた項目の数、 その型、
および C言語のコード内で参照される名前 です。 その次に、 各プリミティブの C言語コードの prelude と postlude を生成します。
+
の最終的な C言語コードは以下のようになります:
I_plus: /* + ( n1 n2 -- n ) */ /* label, stack effect */ /* */ /* documentation */ NAME("+") /* debugging output (with -DDEBUG) */ { DEF_CA /* definition of variable ca (indirect threading) */ Cell n1; /* definitions of variables */ Cell n2; Cell n; NEXT_P0; /* NEXT part 0 */ n1 = (Cell) sp[1]; /* input */ n2 = (Cell) TOS; sp += 1; /* stack adjustment */ { n = n1+n2; /* C code taken from the source */ } NEXT_P1; /* NEXT part 1 */ TOS = (Cell)n; /* output */ NEXT_P2; /* NEXT part 2 */ }
これは長くて非効率に見えますが、 GNU C コンパイラーは非常に適切に最適化して、 例えば R3000 や HP RISC マシンなどで
+
に最適なコードを生成します。 n
達を定義してもコードは生成されませんし、 また、
それら中間ストレージとして使用してもコストはかかりません。
この例では示されていない他の最適化もあります。 単純な変数間の代入は通常はコストがかかりません(コピー伝播)。 スタック項目の 1
つがプリミティブによって使用されていない場合(drop
など)、
コンパイラーはスタックのその部分の負荷を削除します(デッド・コードの削除)。 一方、 コンパイラーが実行しない処理もいくつかあり、 そのため、 それらは
prims2x.fs によって実行されます。 コンパイラは、 スタック項目を元の場所に格納するコードを最適化しません(例:
over
).
プリミティブのプログラミングは通常は簡単ですが、 最も顕著な ?dup
だけでなく、 NEXT
に(常に)フローが流れていかないワードについても、 プログラマがジェネレーターのアクションを考慮する必要があるケースがいくつかあります。
詳細について(原文未記述)