このマニュアルは、 仮想マシン・インタープリタ・ジェネレーターである Vmgen (バージョン 0.7.9_20240418、April 18, 2024) 用です
Author: Anton Ertl Copyright © 2002,2003,2005,2007,2008,2019 Free Software Foundation, Inc.
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, with the Front-Cover texts being “A GNU Manual,” and with the Back-Cover Texts as in (a) below. A copy of the license is included in the section entitled “GNU Free Documentation License.”
(a) The FSF’s Back-Cover Text is: “You have freedom to copy and modify this GNU Manual, like GNU software. Copies published by the Free Software Foundation raise funds for GNU development.”
Vmgen は、 効率的なインタープリターを作成するためのツールです。 これは、 単純な仮想マシンの記述を受け取り、 さまざまな方法で仮想マシン・コードを処理する(特に、 それを実行する)ための効率的な C 言語のコードを生成します。 その結果得られるインタープリターの実行効率は、 最適化コンパイラーによって生成されるマシンコードの10分の1以内に収まるのが普通です。
Vmgen がサポートするインタープリター設計戦略は、 インタープリターを以下の 2 つの部分に分割することです:
このような分割は通常、 モジュール性と効率性のためにインタープリターで使用されます。 仮想マシン・コードは通常、 load-and-go コンパイラーのように、 フロント・エンドとメモリ内の仮想マシン・インタープリターの間で受け渡されます。 これにより、 コードをファイルに書き込んで再度読み取るという複雑さと時間のコストが回避されます。
「仮想マシン」(VM) は、 実際のマシン・コードと同様に、 メモリ内で相互に続く「VM 命令」のシーケンスとしてプログラムを表します。 制御フローは、 実際のマシンと同様に、 VM 分岐命令を通じて発生します。
このセットアップでは、 Vmgen は、 仮想マシン命令の簡単な記述(see Input File Format)から仮想マシン命令を処理するほとんどのコードを生成できます。 特に以下のようなものです:
フロント・エンドで役立ちます。
フロント・エンドのデバッグに役立ちます。
フロント・エンドと VM インタープリターのデバッグに役立ちます。 あなたは、 通常は、 ユーザーのプログラムをソース・レベルでデバッグするための他の手段を提供するでしょう。
スーパー命令(superinstructions)を使用して VM インタープリターを最適化するのに役立ちます(see VM profiler)。
VM 命令を処理しないインタープリター・システムのパーツを作成するには、 他のツール(例: bison
)を使用するか、
手動でコーディングする必要があります。
Vmgen は、さまざまな最適化を通じて効率的なインタープリターをサポートします。 特に
その結果、Vmgen ベースのインタープリターは、 小規模なベンチマークでは、 最適化された C 言語コンパイラーからのネイティブ・コードよりも 1 桁ほど遅いだけです。 ランタイム・システムでより多くの時間を費やす大規模なベンチマークでは、 多くの場合、 速度低下は少なくなります(たとえば、 Vmgen で生成された JVM インタープリターの速度の低下は、 大規模なベンチマークの場合、 測定した最高の JVM JIT コンパイラーと比べてわずか 2 から 3 分の 1 です。 他のいくつかの JIT と、 私たちが調べた他のすべてのインタープリターは、 私たちのインタープリターよりも遅かったです)。
VM は通常、 スタック・シン(VM 命令間のデータを受け渡しをスタック上で行う)として設計されており、 Vmgen はそのような設計を特に適切にサポートします。 ただし、 Vmgen を使用して レジスター型 VM (see Register Machines) を実装することもでき、 それでも Vmgen が提供する利点のほとんどを活用できます。
今現在では実装されていない命令記述(instruction descriptions)には多くの潜在的な用途がありうるものと思います。 私達は機能のリクエストは受け付けており、 誰かがリクエストした場合は新しい機能を検討します。 そのため、 上記の機能リストはすべてを網羅したものではありません。
インタープリターは、 以下の 3 つの利点をすべて兼ね備えた、 人気のある言語実装手法です:
Vmgen を使用すると、 インタープリターの実装がさらに簡単になります。
インタープリターの主な欠点は、 実行速度です。 ただし、 実行速度の分野では、 インタープリターごとに大きな違いがあります。 単純な操作で構成されるプログラム上の最適化された C 言語コードの速度の低下は、 通常、 より効率的なインタープリターでは 10 倍、 効率の低いインタープリタでは 1000 倍になります(複雑な操作を実行するためのライブラリーで費やされる時間はすべての実装戦略で同一であるため、 複雑な操作を実行するプログラムの速度低下は少なくなります)。
Vmgen は、 効率的なインタープリターを構築するための手法をサポートしています。
インタープリター・ステムは通常、 入力言語をパースしてプログラムの中間表現を生成する「フロント・エンド」と、 プログラムの中間表現を実行するインタープリターに分かれます。
効率的なインタープリターの場合、 選択される中間表現は(抽象構文ツリーなどではなく、 )仮想マシン・コードです。 「仮想仮想マシン」(VM) コードは、 メモリー内に順番に配置された VM 命令で構成されます。 これらは VM インタープリターによって順番に実行されますが、 VM 分岐命令は制御フローを変更することができ、 制御構造の実装に使用されます。 実際のマシン・コードと概念的に類似しているため、 「仮想マシン」という名前が付けられます。 実機の用語に類似したさまざまな用語が使用されています。 たとえば、 「VM レジスター」(命令ポインターやスタック・ポインターなど)があり、 VM 命令は 「オペコード」と 「直接引数」(immediate arguments)で構成されます。
このフレームワークでは、 Vmgen は VM インタープリターと、 VM 命令を処理するその他のコンポーネントの構築をサポートします。 VM
コード生成のサポートを除いて、 フロント・エンドのサポートはありません。 フロント・エンドは、 flex
や
bison
などのツールでサポートされる、 従来のコンパイラー・フロント・エンド手法を使用して実装できます。
中間表現は通常、 インタープリターの内部にのみ存在しますが、 一部のシステムでは、 イメージ・ファイルとして、 または完全なリンク可能なファイル形式(JVM など)でのファイルへの保存もサポートしています。 現在、 Vmgen ではそのような機能に対する特別なサポートはありませんが、 命令記述に記載されている情報が役に立つ可能性があります。 また、 私達は機能のリクエストや提案をお待ちしています。
ほとんどの VM は、 VM 命令間で一時データを渡すために 1 つ以上のスタックを使用します。 私達は、 通常、 スタック・アーキテクチャを使用する方が簡単かつ高速であると考えていますが、 もう一つのオプションとして、 仮想マシンにレジスター・マシン・アーキテクチャを使用することが考えられます。
ただし、 このオプションはスタック・マシン・アーキテクチャよりも実装に時間がかかるか、 非常に複雑です。
Vmgen にはスタック VM に対する特別なサポートと最適化があり、 スタック VM の実装が簡単かつ効率的に行えます。
また、 Vmgen (see Register Machines) を使用して レジスター VM を実装することもできます。 その場合でも、 あなたは Vmgen のほとんどの機能を活用できます。
スタック項目はすべて同一サイズなので、 通常は整数やポインターや浮動小数点数値は同じサイズ幅になります。 Vmgen は、 2 つの連続したスタック項目を単一の値として扱うことをサポートしていますが、 それより大きいものは、 スタックに置いた、 データへのポインターを使用して、 他のメモリ領域(ヒープなど)に保持するのが最適です。
もう 1 つのデータ源は、 (VM 命令ストリーム内の) 即時引数 VM 命令達(immediate arguments VM instructions)です。 VM 命令ストリームは、 Vmgen のスタックと同様に処理されます。
Vmgen には、 「ガーベジ・コレクション」に対するサポートも制限も組み込まれていません。 ガベージ・コレクションが必要な場合は、 ランタイム・ライブラリーでそれを提供する必要があります。 「参照カウント」(reference counting)を使用するのはおそらく困難、 可能な場合もあります(興味がある場合は私達にお問い合わせください)。
Vmgen を使用する場合、 このセクションを理解する必要はおそらくありませんが、 役立つ場合もあります。 ここでスキップして、 あとでディスパッチ方法に関する記述がわかりにくい場合にこのセクションを読むといいでしょう。
1 つの VM 命令を実行した後、 VM インタープリターは次の VM 命令にディスパッチ(dispatch;切り替え作業)する必要があります(Vmgen はディスパッチ・ルーチン「NEXT」を呼び出します)。 Vmgen は 2 つのディスパッチ方法をサポートしています:
この方法では、 VM インタープリターに巨大な switch
ステートメントが含まれており、 VM 命令ごとに 1 つの
case
があります。 VM 命令のオペコードは、 VM コード内の整数(enum
によって生成されるなどする)によって表され、 ディスパッチは、 次のオペコードをロードし、 switch
して、 適切な
case
で続行することによって行います。 VM 命令の実行後、 VM インタープリタはディスパッチ・コードに戻ります。
このメソッドは、 VM 命令を実行するためのマシン・コード断片の開始アドレスによって VM 命令オペコードを表します。 ディスパッチ(切り替え作業)は、 このアドレスのロードと、 そこへのジャンプと VM 命令ポインターのインクリメントで構成されます。 通常、 スレッド化コードのディスパッチ・コードは、 VM 命令を実行するコードに直接追加されます。 スレッド化コードは ANSI C では実装できませんが、 GNU C の label-as-values 拡張機能を使用して実装できます(see Labels as Values in GNU C Manual)。
インタープリターやベンチマークやマシンによっては、 スレッド化コードはスイッチ・ディスパッチより 2 倍早くなることがあります。
Vmgen を呼び出す通常の方法は以下のとおりです:
vmgen inputfile
ここで inputfile は VM 命令記述ファイルで、 通常は .vmg で終わります。 それぞれの出力ファイル名は、
inputfile のベース名を取得し(つまり、 出力ファイルは現在の作業ディレクトリに作成されます)、 .vmg
の部分を、 -vm.i と -disasm.i と -gen.i と -labels.i と
-profile.i と -peephole.i とにに置き換えることによって作成されます。
たとえば、vmgen hack/foo.vmg
なら、 foo-vm.i と foo-disasm.i
と foo-gen.i と foo-labels.i と foo-profile.i と
foo-peephole.i を作成します。
Vmgen でサポートされるコマンドライン・オプションは以下のとおりです
Vmgen を使用する同一の例として、 vmgen-ex と vmgen-ex2 の 2 つのバージョンがあります(例として Gforth もありますが、 追加の(文書化されていない)機能が使用されており、 他のいくつかの点でも異なります)。 この例では、 JavaVM のような小さな仮想マシンを備えた小さな Modula-2 のような言語である「mini」言語を実装しています。
2つの例の違いは、 vmgen-ex は多くのキャスト(cast)を使用するのに対し、 vmgen-ex2 はほとんどのキャストを回避し、 代わりに共用体(unions)を使用することです。 このマニュアルの残りの部分では、 通常、 vmgen-ex 内のファイルについてのみ言及します。 共用体(union)を使用したい場合は、 vmgen-ex2 内の同等のファイルを使用してください。
Makefile README disasm.c wrapper file engine.c wrapper file peephole.c wrapper file profile.c wrapper file mini-inst.vmg simple VM instructions mini-super.vmg superinstructions (empty at first) mini.h common declarations mini.l scanner mini.y front end (parser, VM code generator) support.c main() and other support functions fib.mini example mini program simple.mini example mini program test.mini example mini program (tests everything) test.out test.mini output stat.awk プロファイル情報を集約するためのスクリプト peephole-blacklist スーパー命令で許可されない命令のリスト seq2rule.awk スーパー命令を生成するためのスクリプト
あなた独自のインタープリターを作る場合、 通常、 以下のファイル群はコピーして使い、 変更することはほとんどないでしょう:
disasm.c wrapper file engine.c wrapper file peephole.c wrapper file profile.c wrapper file stat.awk プロファイル情報を集約するためのスクリプト seq2rule.awk スーパー命令を生成するためのスクリプト
そして、 通常、 あなたは、 以下のファイル群を大幅に変更するか、 置き換える事になります:
Makefile mini-inst.vmg simple VM instructions mini.h common declarations mini.l scanner mini.y front end (parser, VM code generator) support.c main() and other support functions peephole-blacklist スーパー命令で許可されない命令のリスト
example のディレクトリに cd
してから make
と入力すると、 example をビルドできます。
make check
で動作することを確認できます。 以下のようにして mini 言語のプログラムを実行できます:
./mini fib.mini
オプションについて詳しくは、 ./mini -h
と入力してください。
作者は Makefile には、 プロファイリングを使用してスーパー命令を作成するためのルールを追加していません(スーパー命令選択の選択肢はたくさんありますが、 それらの選択肢を Makefile にハードコードしたくありませんでした)が、 サポートするスクリプトがいくつかあります。 例:
fib.mini と test.mini をトレーニング・プログラムとして使用すると、 以下のようなプロファイルが得られます:
make fib.prof test.prof #数秒かかります
これらのプロファイルは stat.awk で集約できます:
awk -f stat.awk fib.prof test.prof
結果には以下のような行が含まれます:
2 16 36910041 loadlocal lit
これは、 シーケンス loadlocal lit
が 2 つのプロファイルで合計 16 回静的に発生し、 動的実行数が 36910041
であることを意味します。
数値はスーパー命令を選択するためにさまざまな方法で使用できます。 たとえば、 動的実行数が 10000 を超えるシーケンスをすべて選択したい場合は、 以下のパイプラインを使用します:
awk -f stat.awk fib.prof test.prof| awk '$3>=10000'| #シーケンスを選択 fgrep -v -f peephole-blacklist| #良くない命令を排除 awk -f seq2rule.awk| #シーケンスをスーパー命令ルールに変換 sort -k 3 >mini-super.vmg #シーケンスを並べ替え
ファイル peephole-blacklist には、スタックまたはスタック・ポインター(mini 言語の場合: call
、return
)に直接アクセスするすべての命令が含まれています。 並べ替え手順は、 プレフィックスが、
大きなスーパー命令よりも前に配置されるようにするために必要です。
いまや、 あなたは ‘make’ と言うだけでスーパー命令を備えたバージョンの mini 言語を作成できるようになりました。
Vmgen は、 仮想マシン命令の仕様を含むファイルを入力として受け取ります。 通常、 このファイルの名前は .vmg で終わります。
ほとんどの例は vmgen-ex のを使っています。
文法は EBNF 形式で、 a|b
は「a または b」、
{c}
は c の 0 回以上の繰り返しを意味し、 [d]
は d の
0 回または 1 回の繰り返しを意味します。
Vmgen の入力は自由書式ではないため、 改行(および場合によっては空白)をどこに入れるかに注意する必要があります。
description: {instruction|comment|eval-escape|c-escape} instruction: simple-inst|superinst simple-inst: ident '(' stack-effect ')' newline c-code newline newline stack-effect: {ident} '--' {ident} super-inst: ident '=' ident {ident} comment: '\ ' text newline eval-escape: '\E ' text newline c-escape: '\C ' text newline
注意: この文法の \
は、 印刷不可能な文字の C 言語スタイルのエンコーディングではなく、
文字通りの意味であることに注意してください。
simple-inst
で C 言語コードを区切る(delimit)方法は 2 つあります:
comment
と eval-escape
と c-escape
内の text
には改行を含めることはできません。 Ident
は、 C 言語識別子の通常の規則に準拠する必要があります(そうしないと、 Vmgen
出力で C 言語のコンパイラーが詰まり(choke)ます)。 Ident
は、 stack-effect
の ident
にスタック・プレフィックス(スタック・プレフィックス構文については see Eval escapes 参照)が付いている場合がある点を除き、 C
言語識別子の通常の規則に準拠する必要があります。
c-escape
は text を各出力ファイルに渡します(‘\C’ 無しで)。
これは主に条件付きコンパイルに役立ちます(つまり、 ‘\C #if ...’ などを記述します)。
文法で指定された構文に加えて、 Vmgen は ‘m4 -s’ (see Invoking m4 in GNU m4)や、 同様のツールによって生成された同期行(sync lines)(‘#line’ で始まる行)も処理します。 これにより、 C 言語コンパイラーのエラー・メッセージを C 言語コードの元のソースに関連付けることができます。
Vmgen は、 ここで説明した文法を超えたいくつかの拡張機能を理解しますが、 これらの拡張機能は Gforth を構築する場合にのみ役立ちます。 Gforth に使用される書式の説明は prim にあります。
eval-escape
内の text は、 Vmgen が行を読み取るときに評価(eval)される Forth コードです。 通常、
この機能を使用してスタックと型を定義します。
あなたが Forth を知らない (そして学びたくない) 場合は、 以下の文法に従って text を作成できます。 これらのルールは通常、 Vmgen を使用するために必要な Forth の全てです:
text: stack-decl|type-prefix-decl|stack-prefix-decl|set-flag stack-decl: 'stack ' ident ident ident type-prefix-decl: 's" ' string '" ' ('single'|'double') ident 'type-prefix' ident stack-prefix-decl: ident 'stack-prefix' string set-flag: ('store-optimization'|'include-skipped-insts') ('on'|'off')
このコードの構文は完全にはチェックされていないことに注意してください(eval-escape で記述できる Forth プログラムの断片は他にも多数あります)。
スタック・プレフィックスには文字または数字または ‘:’ を含めることができ、‘#’ で始まる場合もあります。 たとえば、Gforth では、 リターン・スタックにはスタック・プレフィックス ‘R:’ が付きます。 この制限は、 スタック・プレフィックスの定義ではチェックされませんが、 後でスタック項目のパース・ルールによって強制されます。
あなたが既に Forth をご存知の場合、 関連する非標準ワードのスタック効果は以下のとおりです:
stack ( "name" "pointer" "type" -- ) ( name execution: -- stack ) type-prefix ( addr u item-size stack "prefix" -- ) single ( -- item-size ) double ( -- item-size ) stack-prefix ( stack "prefix" -- ) store-optimization ( -- addr ) include-skipped-insts ( -- addr )
item-size はスタック上で 3 つのセルを占有します。
例として、 以下の簡単な VM 命令の記述を使用します:
sub ( i1 i2 -- i ) i = i1-i2;
最初の行には、 VM 命令の名前 (sub
) とそのスタック効果 (i1 i2 -- i
) を指定します。
最初の行以外の記述の残りの部分は単なる C 言語コードです。
スタック効果では、 sub
がデータ・スタックから 2 つの整数を取得し、 それらを C 言語の変数 i1
と
i2
に格納することを指定します(右端の項目 (i2
) が、 スタックのトップです: i1
をスタックにプッシュし、 次に i2
をスタックにプッシュすると、 結果のスタック状態は i1 i2
になります)。
そしてその後、 その後 1 つの整数 (i
) をスタックにプッシュします(右端の項目がスタックの一番上になります)。
スタック項目の型とスタックを知るにはどうすればよいでしょうか? Vmgen は、 Fortran と同様にプレフィックスを使用します。 Fortran とは対照的に、 あなたは最初にプレフィックスを定義する必要があります:
\E s" Cell" single data-stack type-prefix i
これは、 型 Cell
(mini.h では long
として定義)を参照するプレフィックス i
を定義し、 デフォルトでは data-stack
を参照します。 また、 この型が 1
つのスタック項目(single
)を占めることも指定します。 型プレフィックスは変数名の一部です。
data-stack
を使用する前に、 この方法で data-stack
を定義する必要があります:
\E stack data-stack sp Cell
この行は、 スタック・ポインター sp
を使用するスタック data-stack
を定義し、 各項目の基本タイプは
Cell
です。 他の型は 1 つまたは 2 つの Cell
に収まる必要があり(型の幅が single
か
double
かによって異なります)、 型キャスト・マクロ(see VM engine)を使用して
data-stack
にアクセスするときに Cell との間でキャストされます。 Vmgen 製インタープリターではスタックは、
デフォルトでは、 より低いアドレスに向かって伸長します(see Stack growth direction)。
スタック・プレフィックスを使用して、 スタック項目のデフォルト・スタックをオーバーライドできます。 たとえば、 以下の命令について考えてみましょう:
lit ( #i -- i )
VM 命令 lit
は、 命令ストリームから(プレフィックス #
で示される) 項目 i
を取得し、
それを(デフォルトの)データ・スタックにプッシュします。 スタック・プレフィックスは変数名の一部ではありません。
スタック・プレフィックスは以下のように定義されています:
\E inst-stream stack-prefix # \E data-stack stack-prefix S:
この定義は、 スタック・プレフィックス #
が「スタック inst-stream
」を指定することを定義します。
命令ストリーム(inst-stream
)は通常のスタックとは振る舞いが少し異なるため、 事前に定義済であり、
あなたが定義する必要はありません。
命令ストリームには命令とその直接引数(immediate arguments)が含まれるため、
引数が命令ストリームからのものであることを指定することは、 直接引数であることを示しています。 もちろん、 命令ストリーム引数は、 スタック効果の
--
の左側にのみ出現できます。 複数の命令ストリーム引数がある場合、 (あなたの直観どおり)左端が最初の引数になります。
この機能は、 ここで説明されている(デフォルトで呼び出される) vmgen の 0.6.2 バージョンでは必要なく、 サポートされていません。
上記のスタック効果の仕様を使用して、 すべてのスタック効果を指定できるわけではありません。 他のスタック効果がある VM 命令の場合は、 C 言語コードでスタック・ポインターにアクセスすることで明示的に指定できます。 ただし、 そのような明示的なスタック・アクセスを Vmgen に通知する必要があります。 そうしないと、 Vmgen の最適化が、 この明示的なスタック・アクセスと競合する可能性があります。
Vmgen に通知するには、 ...
と適切なスタック・プレフィックスをスタック・コメントに入れます。 次に、 VM
命令はまずスタック効果で指定された他のスタック項目を C 言語の変数に取り込み、 次にそのスタックの他のすべてのスタック項目がメモリ内にあり、
スタックのスタック・ポインターが先頭を指していることを確認します(デフォルトでは。 あなたがスタック・アクセス変換(the stack access
transformation)を変更しない限り: see Stack growth direction)。
一般的なルールは次のとおりです: VM 命令の C 言語コードでスタック・ポインターに言及する場合は、 スタック効果にそのスタックの
...
を含める必要があります。
以下の例について考えてみましょう:
return ( #iadjust S:... target afp i1 -- i2 ) SET_IP(target); sp = (Cell *)(((char *)sp)+iadjust); fp = afp; i2=i1;
最初に変数 target afp i1
がスタックからポップされ、 次にスタック・ポインター sp
が新しいスタックの深さに正しく設定され、 次に C 言語のコードがスタックの深さを変更して他の処理を行い、 最後に i2
は新しい深さでスタックにプッシュされます。
スタック効果内の ...
の位置は関係ありません。 異なるスタックに複数の ...
を使用することも、
同一のスタックに複数の ...
を使用することもできます(追加の効果はありません)。 スタック・プレフィックスなしで
...
を使用すると、 命令ストリームを除くすべてのスタックが指定されます。
命令ストリームに ...
を使用することはできませんが、 その必要はありません。 C 言語のコードの先頭で、 IP
は次の
VM 命令の先頭(つまり、 現在の VM 命令の末尾のすぐ次)を指し、 SET_IP
を使用して命令ポインターを変更できます(see VM engine)。
Vmgen は、 単純な命令の C 言語コード部分にある以下の文字列を認識します:
SET_IP
¶Vmgen に関する限り、 これを含む VM 命令は、 (プロファイルされたシーケンスを区切るためにプロファイリングで使用される、) VM 基本ブロックを終了します。 C 言語のレベルでは、 これにより命令ポインターも設定されます。
SUPER_END
¶これにより、 命令に SET_IP
が含まれていない場合でも、 (プロファイリング用の)基本ブロックが終了します。
INST_TAIL;
¶Vmgen は、 ‘INST_TAIL;’ を、 VM 命令を終了して次の VM 命令をディスパッチするためのコードに置き換えます。 ‘INST_TAIL;’ がなくても、 制御が C g言語コードの最後に到達すると、 これは自動的に行われます。 これを C 言語コードの途中に置きたい場合に ‘INST_TAIL;’ を使用する必要があります。 典型的な例は、 以下のような条件付き VM 分岐です:
if (branch_condition) { SET_IP(target); INST_TAIL; } /* implicit tail follows here */
この例では、 if ステートメントの後に ‘INST_TAIL;’ が暗黙的に存在するため、 厳密には ‘INST_TAIL;’ は必要ありませんが、 これを使用すると分岐予測の精度がわずかに向上し、 他の最適化が可能になります。
SUPER_CONTINUE
¶これは、 VM 命令に SET_IP
が含まれている場合でも、 VM 命令の終わりの暗黙の末尾に連続して次の VM
命令をディスパッチすることを示します。 これにより、 vmgen-ex コードにはまだ実装されていない(しかし
Gforthには実装されている)最適化が可能になります。 典型的なアプリケーションは以下のような条件付き VM 分岐内にあります:
if (branch_condition) { SET_IP(target); INST_TAIL; /* now this INST_TAIL is necessary */ } SUPER_CONTINUE;
注意: Vmgen は C 言語レベルのトークン化またはコメントまたは文字列または条件付きコンパイルについては賢明ではないため、 コメント・アウトされた SUPER_END であっても基本ブロックの終了として(または、 例えば、 ‘RESET_IP;’ を ‘SET_IP;’ として)解釈されることに注意してください。 逆に、 Vmgen ではこれらの文字列が文字通り存在する必要があります。 C 言語のプリプロセッサ・マクロ内に隠れている場合、 Vmgen はそれらを認識できません。
Vmgen は、ユーザー指定の C 言語コードがスタック・ポインターやスタック項目にアクセスせず、 命令ポインターへのアクセスは特殊なマクロを介してのみ発生するという想定に基づいて、 コードを生成し、 いくつかの最適化を実行します。 一般に、 あなたはこれらの制限に注意する必要があります。 ただし、 あなたがこれらの制限を破る必要がある場合は、 以下をお読みください。
スタックまたはスタック・ポインターに直接アクセスすると、 いくつかの理由で問題が発生する可能性があります:
命令ポインターには、 そのための特別なマクロ(‘IP’, ‘SET_IP’, ‘IPTOS’)を介してのみアクセスする必要があります。 これにより、 これらのマクロをいくつかの方法で実装して最高のパフォーマンスを実現できるようになります。 ‘IP’ は次の命令を指していて、 ‘IPTOS’ はその内容です。
デフォルトでは、 スタックは下位アドレスに向かって伸長します。 これを変更するには、 スタックの
stack-access-transform
フィールドを、 適切なインデックス変換を実行する xt ( itemnum --
index )
に設定します。
たとえば、 data-stack
をより高いアドレスに向かって伸長させ、
スタック・ポインターが常にスタックの最上位のすぐ先を指すようにする場合は、 data-stack
を定義した直後に以下を使用します:
\E : sp-access-transform ( itemnum -- index ) negate 1- ; \E ' sp-access-transform ' data-stack >body stack-access-transform !
これは、 sp-access-transform
を使用して data-stack
にアクセスするためのインデックスを生成することを意味します。 上記の sp-access-transform
の定義は、 n を
-n-1 に変換します(例: 1 を -2 にする)。 これは、 sp[-1] の 0 番目のデータ・スタック要素(スタックの先頭)、 sp[-2]
の 1 番目のデータースタック要素などにアクセスします。 これは、 上向きに成長するスタックが使用される一般的な方法です。
あなたが別のインデックス変換が必要で、 それをプログラムするのに十分な Forth の知識がない場合は、 我々にお尋ね下さい。
注意: (静的な)スーパー命令(superinstructions)にあまり多くの作業を費やさないでください。 Vmgen の将来のバージョンでは、 動的スーパー命令がサポートされる予定で(Ian Piumarta and Fabio Riccardi, Optimizing Direct Threaded Code by Selective Inlining, PLDI’98 参照)、 静的スーパー命令の利点ははるかに少なくなっています(暫定的な結果は、 係数 1.1 の高速化)。
以下にスーパー命令(superinstruction)定義の例を示します:
lit_sub = lit sub
lit_sub
はスーパー命令(superinstruction)の名前で、 lit
と sub
はそのコンポーネントです。 このスーパー命令は、 シーケンス「lit
sub
」と同じアクションを実行します。 これは、
そのシーケンスが発生するたびに VM コード生成関数によって自動的に生成されるため、 このスーパー命令を使用したい場合は、
この定義を追加するだけで済みます(see VM profiler により部分的に自動化することもできます)。
Vmgen では、コンポーネントの命令群が、 コンポーネントを使用するスーパー命令(superinstructions)より前に定義された単純な命令(simple instructions)であることが必要です。 現在、 Vmgen では、 スーパー命令の先頭にあるすべてのサブ・シーケンス(プレフィックス)が当該スーパー命令より前にスーパー命令として定義されている必要もあります。 つまり、 スーパー命令を定義したい場合
foo4 = load add sub mul
あなたは、 まず、 load
と add
と sub
と mul
を定義する必要があります。
さらに
foo2 = load add foo3 = load add sub
ここで、 sumof4
は sumof5
の最長のプレフィックスで、 sumof3
は
sumof4
の最長のプレフィックスです。 (訳注: 推敲途中だったかな? 意味不明)
注意: Vmgen は、 生成するコードのみがスタック・ポインターや、 命令ポインターや、 さまざまなスタック項目にアクセスすると想定しており、 この想定に基づいて最適化を実行することに注意してください。 したがって、 C 言語コードで命令ポインターを変更する VM 命令は、 最後のコンポーネントとしてのみ使用する必要があります。 C 言語コードがスタック・ポインターにアクセスする VM 命令は、 コンポーネントとしてまったく使用しないでください。 Vmgen はこれらの制限をチェックsせず、 インタープリターでバグが発生するだけです。
Vmgen フラグ include-skipped-insts
は、
スーパー命令(superinstruction)コードの生成に影響します。 現在、 のぞき穴最適化(peephole
optimizer)では両方のバリエーションはサポートされていないため、 このフラグは今のところそのままにしておきます。
格納最適化(store optimization)というマイナーな最適化(Gforth の実行命令の 0.6% ~ 0.8% 削減)では、 命令の記述に追加の要件が課されるため、 デフォルトでは無効になっています。
これはは何をするのでしょうか? 以下のような命令を考えてみましょう
dup ( n -- n n )
簡単にするために、 スタックのトップをレジスタにキャッシュしていないことも仮定します。 今、 dup の C 言語コードは、 最初に n
をスタックからロードし、 次にそれをスタックに 2 回 格納し、 そのうち 1 回は元のアドレスに格納します。 元のアドレスに格納する回は不要ですが、
gcc はそれを最適化しないので、 代わりに vmgen がそれを行うことができます(格納最適化(store
optimization)をオンにしている場合)。
Vmgen は、 スタック項目の名前を使用して、 スタック項目に開始時と同一の値が含まれているかを決めます。 したがって、 格納最適化を使用する場合は、 入力と出力で同じ名前を持つスタック項目も同じ値を持ち、 提供する C 言語コードで変更されないことを確認する必要があります。 つまり、 格納最適化をオンにすると、 以下のコードは失敗する可能性があります:
add1 ( n -- n ) n++;
代わりに、 あなたは別の名前を使用する必要があります。 つまり以下のようにします:
add1 ( n1 -- n2 ) n2=n1+1;
同様に、 格納最適化では、 スタック・ポインターが Vmgen で生成されたコードによってのみ変更されることを前提としています。 C 言語コードでスタック・ポインターを変更する場合は、 入力スタック項目と出力スタック項目に異なる名前を使用して(おそらく間違った)格納最適化を回避するか、 この VM 命令の格納最適化をオフにしてください。
格納最適化を有効にするには、 以下のように記述します
\E store-optimization on
これをファイルの先頭に記述します。 任意の 2 つの VM 命令記述の間で、 この最適化をオンまたはオフにできます。 再度オフにするには、 以下を使用できます。
\E store-optimization off
Vmgen を使用してスタック VM ではなくレジスター VM を実装する場合は、 直接実行する方法とスーパー命令を使用する方法の 2 つがあります。
直接的な方法を使用する場合は、 以下のように、 レジスタ番号を直接の引数として受け取る命令を定義します:
add3 ( #src1 #src2 #dest -- ) reg[dest] = reg[src1]+reg[src2];
この方法の欠点は、 トレース中にレジスター番号のみが表示され、 レジスタの内容は表示されないことです。 実際には、
printarg_src
(see VM engine)を適切に定義すると、 開始時にソース・レジスターの値を出力できますが、
終了時に宛先レジスターの値を出力することはできません。
スーパー命令(superinstructions)を使用してレジスター VM を定義する場合は、 以下のように、 スタックを使用する単純な命令(simple instructions)を定義してから、 全体的なスタック効果のないスーパー命令を定義します:
loadreg ( #src -- n ) n = reg[src]; storereg ( n #dest -- ) reg[dest] = n; adds ( n1 n2 -- n ) n = n1+n2; add3 = loadreg loadreg adds storereg
この方法の利点は、 トレース中にレジスター番号だけでなく値も表示されることです。 この方法の欠点は、 現時点ではスーパー命令(superinstructions)を直接生成できず、 単純な命令(simple instructions)のシーケンスを生成することによってのみ生成できることです(需要があれば、 作者達は将来これを変更するかもしれません)。
上記の問題とは別に、 レジスター VM のサポートを改善可能でしょうか? VM インタープリターに関連して「レジスター・マシン」という用語を使用する場合、 人によって意味が異なるため、 一般的な方法でこれを行う方法を理解するのは困難です。 ただし、 その方向でアイデアや要望がある場合は、 著者にお知らせください(see Contact)。
以下のエラー・メッセージが Vmgen によって作成されます:
# can only be on the input side
¶【# は入力側にのみ指定できます】 ‘--’ (出力側) の後側で命令ストリーム(instruction-stream)のプレフィックス(通常は ‘#’)を使用しました。 これは前側(入力側)でのみ使用できます。
the prefix for this superinstruction must be defined earlier
¶【このスーパー命令(superinstruction)のプレフィックスは事前に定義する必要があります】 直接プレフィックス(たとえば ab
= a b
)を定義せずにスーパー命令(superinstruction)定義(たとえば abc = a b c
)を行いました。
See Superinstructions
sync line syntax
¶【同期行構文】プリプロセッサ(たとえば m4
)を使用して Vmgen 入力コードを生成している場合は、 #line
(別名 sync lines;同期行)ディレクティブを作成するとよいでしょう。 このエラーは、 そのような行が Vmgen
が期待する構文に無いことを示します(こんなことが起こってはいけません。 問題の行をバグ・レポートで報告してください)。
syntax error, wrong char
¶【構文エラー、 不正な文字】構文エラーです。 エラーの場所がすぐにわからない場合は、 次の点を確認すると役立つ場合があります: C 言語のコードが波かっこ({};braces)で区切られていない VM 命令に空行を入れていませんか? 波かっこで区切られた C 言語のコードを使用した場合、 先頭に空白を入れずに、 行の先頭に区切りの波かっこを置きましたか? 区切りの波かっこを忘れせんでしたか?
too many stacks
¶【スタックの数が多すぎます】Vmgen は現在、3 つのスタック(に加えて命令ストリーム)をサポートしています。 さらに必要な場合は作者にご連絡ください。
unknown prefix
¶【不明のプレフィックスです】スタック項目が定義された型プレフィックスと一致しません(スタック・プレフィックス削除後)。 そのスタック項目に必要な型プレフィックスを宣言するか、 別の型プレフィックスを使用する必要があります
unknown primitive
¶【不明のプリミティブです】最初に単純な VM 命令(simple VM instruction)を定義せずに、 スーパー命令(superinstruction)定義で単純 VM 命令の名前を使用しました。
さらに、 C 言語のコンパイラは、 Vmgen によって生成されたコードが原因でエラーを生成する可能性があり、 たとえば、 あなたは型キャスト関数を定義する必要があるかもしれません。
Vmgen で動作する VM インタープリターを作成する最も簡単な方法は、 おそらく vmgen-ex から始めて、 目的に応じて変更することです。 この章では、 さまざまなラッパーと、 生成されたファイルの機能について説明します。 また、 生成されたコードで使用されるマクロや変数などの、 リファレンス・マニュアル形式の説明も含まれており、 リファレンス・マニュアル形式の説明は最初に読むときはスキップできます。
VM エンジンは、 VM コードを実行するための VM インタープリターです。 それはインタープリター的システムにとって不可欠なものです。
Vmgen は、 VM 命令ディスパッチ(dispatch;切り替え作業)の 2 つの方法をサポートしています。 「スレッド化コード」(高速ですが gcc 固有)と、 「スイッチ・ディスパッチ」(低速ですが、 C 言語コンパイラ間で移植可能)です。 条件付きコンパイル(‘defined(__GNUC__)’)を使用してこれらの方法を選択でき、 著者達の例ではそうしています。
どちらの方法でも、 VM エンジンは C 言語レベルの関数に含まれています。 Vmgen は関数の内容のほとんどを生成しますが、 この関数の定義と、エンジンで使用されるマクロと変数の定義と、 変数の初期化を行う必要があります。 私達の例では、 エンジン関数には name-labels.i も含まれています(see VM instruction table)。
コードの実行に加えて、 VM エンジンはオプションで、 実行された命令や、 実行された命令の引数や、 実行された命令の結果のトレースを出力することもできます。 スーパー命令(superinstructions)の場合は、 コンポーネント命令のみが実行されたかのようにトレースを出力します。 これにより、 古いスーパー命令と比較可能なトレースを維持しながら、 新しいスーパー命令を導入することができます(これは デグレ・チェック(regression tests)のために重要です)。
トレース・コードを出力するかどうかを各命令でチェックするとパフォーマンスが大幅に低下するため、 エンジンのコピーを 2 つ作成することをお勧めします。 1 つは高速実行用、 もう 1 つはトレース用です。 例については、 vmgen-ex/Makefile の engine.o と engine-debug.o の makeルールを参照してください。
以下のマクロと変数が name-vm.i で使用されます:
LABEL(inst_name)
¶これは、 ジャンプまたは switch
ラベルを提供するために各 VM 命令の直前で使用されます(‘:’ は Vmgen
によって提供されます)。 スイッチ・ディスパッチの場合、 これは ‘case label:’ に展開されるべきです。
スレッド化コード・ディスパッチの場合は、 これを ‘label:’ に展開するだけです。 どちらの場合も、
label は通常、 名前の競合を避けるために何らかのプレフィックスまたはサフィックスを付けた inst_name です。
LABEL2(inst_name)
¶これは動的スーパー命令(dynamic superinstructions)に使用されます。 現時点では、 これは「無」(nothing)に展開されるべきです。
NAME(inst_name_string)
¶VM 命令の名前をパラメーターとして含む文字列を含む VM 命令を入力すると呼び出されます。 通常の実行では、 これは「無」(nothing)に展開されるはずですが、 トレースの場合は通常、 名前と、 場合によってはその他の情報(この例ではいくつかの VM レジスター)が出力されます。
DEF_CA
¶通常は空(empty)です。 VM 命令の開始時に新しいスコープ内で呼び出されます。 すべての VM 命令中に表示される変数を定義するために使用できます。 このマクロを空ではないものとして定義する場合は、 マクロ定義は ‘;’ で終了させる必要があります。
NEXT_P0 NEXT_P1 NEXT_P2
¶命令ディスパッチ用の 3 つの部品。 さまざまなプロセッサで最高のパフォーマンスを得るために、 さまざまな方法で定義できます(例の engine.c または Gforth の engine/threaded.h を参照)。 ‘NEXT_P0’ は VM 命令の開始直後(ただし ‘DEF_CA’ の後)、 ‘NEXT_P1’ はユーザー指定の C 言語コードの直後、 そして ‘NEXT_P2’ は最後に呼び出されます。 実際のジャンプは ‘NEXT_P2’ によって実行する必要があります(これを実行するのが早すぎると、 VM 命令の重要な部分が実行されなくなります)。
バリエーションの最も単純なモノは、 ‘NEXT_P2’ がすべてを行い、 他のマクロは何も実行しないヤツです。 ‘IP’ や ‘SET_IP’ や ‘IP’ や ‘INC_IP’ や ‘IPTOS’ などの関連マクロも非常に簡単に定義できます。 スイッチ・ディスパッチの場合、 このコードはディスパッチ・コード(この例では ‘goto next_inst;’) へのジャンプだけで構成されます。 直接スレッド化コード(direct threaded code)の場合は、 ‘({cfa=*ip++; goto *cfa;})’ のようなもので構成されます。
コード(普通は ‘cfa=*ip++;’ というコード)を ‘NEXT_P1’ にセットすることは通常問題を引き起こしませんが、 ‘NEXT_P0’ にコードをセットすると、 通常、他のマクロも変更する必要があります(そして、 少なくとも Alpha 版の Gforth については、 コンパイラーは関連するものを自動的にスケジュール設定することが多いため、 それほど大きな価値はありません)。 さらに極端なバリエーションとしては、 コードをさらに離れた、 たとえば前の VM 命令の NEXT_P1 にセットすることです (プリフェッチ。 PowerPC で便利です)。
INC_IP(n)
¶IP
に n を足しこみます。
SET_IP(target)
¶IP
に target をセットします。
vm_A2B(a,b)
¶(A 型の) ‘a’ を (B 型の) ‘b’ に割り当てる型キャスト・マクロ。 これは主に、
スタック項目を変数に入れたりスタック項目から変数へ戻したりするために使用されます。 したがって、 スタックの基本型(この例では
Cell
)と、
そのスタックで使用される型プレフィックス形式(スタック項目を変数に入れる方向とスタック項目から変数へ戻す方向)すべての組み合わせに対してマクロを定義する必要があります。
型プレフィックス形式の場合は、 (C言語の型名ではない)型プレフィックスを型名(‘vm_Cell2Cell’ ではなく
‘vm_Cell2i’)として使用します。 さらに、 スタックの基本型 X に対して(スーパー命令で使われる)
vm_X2X マクロを定義する必要があります。
事前定義された ‘inst-stream’ (命令ストリーム)のスタック基本型は ‘Cell’ です。 同一項目サイズのスタックが必要な場合、 その基本型を ‘Cell’ にすると、 通常、 定義する必要があるマクロの数が減ります。
ただし、 私達の例では大きく異なります: vmgen-ex はこれらのマクロでキャストを使用しますが、 vmgen-ex2 は共用体(union)フィールドの選択(または共用体フィールドへの代入)を使用します。 浮動小数点数(floats)を整数にキャストしたり、 その逆を行うとビット・パターンが変更されることに注意してください(そして、 これはあなたが望む事ではありません)。 この場合の選択肢は、 (一時的な(temporary))共用体を使用するか、 値のアドレスを取得してポインタをキャストし、 それを逆参照(dereference)することです(常に可能であるとは限らず、 しばしばコストがかさみます)。
vm_twoA2B(a1,a2,b)
¶vm_B2twoA(b,a1,a2)
2 つのスタック項目(a1
、a2
)と 2 つのスタック項目を取る型の変数 b
の間の型キャスト。
これは私達の小さな例では発生しませんが、 例として Gforth を参照してください(engine/forth.h の
vm_twoCell2d
を参照してください)。
stackpointer
¶使用されるスタックごとに、 スタック宣言で指定されたスタック・ポインター名が使用されます。 通常のスタックの場合、
これは左辺値に解決される式(l-expression)でなければなりません。 通常、 これはスタックの基本型へのポインタとして宣言された変数です。
‘inst-stream’ (命令ストリーム)の場合、 名前は ‘IP’ で、 プレーンな右辺値を指定できます。 通常、 これは
NEXT_P*
のさまざまな実装間の違いを抽象化するマクロです。
IMM_ARG(access,value)
¶これを "(access)" に展開するように定義します。 これは将来の拡張機能のための単なるプレースホルダーです。
stackpointerTOS
¶stackpointer が指し示すスタックのTOS(スタックのトップ)。 そのスタックに対してスタックのトップ・キャッシュを使用している場合は、 これを変数として定義する必要があります。 そのスタックがスタックのトップ・キャッシュを使用していない場合、 これは ‘stackpointer[0]’ に展開されるマクロである必要があります。 事前定義された ‘inst-stream’ (命令ストリーム)のスタック・ポインターは ‘IP’ と呼ばれるため、 TOS(スタックの最上位)は ‘IPTOS’ と呼ばれます。
IF_stackpointerTOS(expr)
¶stackpointer が指し示すスタックにスタックのトップ・キャッシュが使用されている場合に、 expr を実行するためのマクロ。 つまり、 stackpointer のスタックのトップ・キャッシュがある場合に expr を実行する必要があります。 それ以外の場合は何もしません。
SUPER_END
¶これは VM プロファイラー(see VM profiler)によって使用されます。 通常の操作では何も行わないので、
プロファイリングのためには vm_count_block(IP)
を呼び出す必要があります。
SUPER_CONTINUE
¶これは Vmgen への単なるヒントであり、 C 言語レベルでは何も行いません。
MAYBE_UNUSED
¶gcc-2.7 以降では、 これを __attribute__((unused))
として定義する必要があります。
スーパー命令(superinstructions)のコード内の未使用の変数に関する警告が抑制されます。 これを定義する必要があるのは、
あなたがスーパー命令を使用している場合のみです。
VM_DEBUG
¶これが定義されている場合、 トレース・コードがコンパイルされます(解釈(interpretation)は遅くなりますが、 デバッグは向上します)。 私達の例では、 エンジンの 2 つのバージョンをコンパイルします: 1 つはトレースできない高速実行バージョンで、 もう 1 つはトレースとプロファイリングが可能です。
vm_debug
¶‘VM_DEBUG’ が定義されている場合にのみ必要です。 この変数に true が含まれている場合、 VM 命令はトレース出力を生成します。 いつでもオンまたはオフにできます。
vm_out
¶‘VM_DEBUG’ が定義されている場合にのみ必要です。 トレース出力を印刷するファイルを指定します(‘FILE *’ とタイプします)。
printarg_type(value)
¶‘VM_DEBUG’ が定義されている場合にのみ必要です。 type に適した方法で value
を出力するためのマクロまたは関数です。 これは、 トレース中にスタック項目の値を出力するために使用されます。 Type
は通常、type-prefix
定義で指定された型プレフィックスです(例: ‘printarg_i’)。
スーパー命令(superinstructions)では、 これは現在のスタックの基本タイプです。
スレッド化コードの場合は、 すべての VM 命令のラベルを含んだテーブルを作成する必要もあります。 これは VM コード生成(see VM code generation)に必要であり、 ラベルは外部から見えないため、 エンジン関数内で実行する必要があります。 次に、 VM コード生成関数で使用できるように、 関数の外に渡す(そして ‘vm_prim’ に割り当てする)必要があります。
これは、 VM 命令テーブルを生成するために最初にエンジン関数を呼び出す必要があり、 その後 VM コードを生成した後、 生成された VM コードを実行するためにエンジン関数を再度呼び出す必要があることを意味します(はい、これは醜いです)。 我々のプログラム例では、 エンジン関数を呼び出すこれら 2 つのモードは、 パラメーター ip0 の値によって区別されます(0 に等しい場合はテーブルが渡され、 それ以外の場合は VM コードが実行されます)。 我々の例では、 テーブルを ‘vm_prim’ に割り当て、 それを ‘engine’ から返すことによってテーブルを渡します。
我々の例 (vmgen-ex/engine.c) では、スイッチ・ディスパッチ用のテーブルも構築します。 これは主に均一性を目的として行われます。
スイッチ・ディスパッチの場合、 enum
で case ラベルとして使用される VM 命令オペコードも定義する必要があります。
両方の目的 (VM 命令テーブルと enum )のために、 ファイル name-labels.i が Vmgen によって生成されます。 あなたはこのファイルで使用される以下のマクロを定義する必要があります:
INST_ADDR(inst_name)
¶スイッチ・ディスパッチの場合、 これは name-labels.i のどちらの使用法でもスイッチ・ラベルの名前にすぎません(‘LABEL(inst_name)’ で使用されているのと同一の名前です)。 スレッド化コード・ディスパッチの場合、 これは ‘LABEL(inst_name)’ で定義されたラベルのアドレスです。 アドレスは ‘&&’ で取得されます(see Labels as Values in GNU C Manual)。
Vmgen は、フロント・エンドが VM コードを生成するために呼び出すことができる VM コード生成関数を name-gen.i に生成します。 これはインタープリター・システム(interpretive system)には不可欠なものです。
VM 命令 ‘x ( #a b #c -- d )’ の場合、 Vmgen は以下のプロトタイプを持つ関数を生成します
void gen_x(Inst **ctp, a_type a, c_type c)
ctp
引数は、 次の命令へのポインターを指します。 *ctp
は生成関数によって増加されます。 つまり、
あなたは生成するコード用のメモリを事前に割り当て、 このメモリ領域の先頭に *ctp を設定して開始する必要があります。 メモリが不足する前に、
新しい領域を割り当て、 新しい領域への VM レベルのジャンプを生成します(このオーバーフロー処理は我々の例では実装されていません)。
他の引数は、 VM 命令の直接引数(immediate arguments)に対応します(type_prefix
宣言で定義されている適切な型を使用します)。
以下の型や変数や関数が name-gen.i で使用されます:
Inst
¶VM 命令のタイプ。 スレッド化コードを使用する場合、 これは void *
です。 スイッチ・ディスパッチの場合、 これは整数型です。
vm_prim
¶VM 命令テーブル (タイプ: Inst *
。 see VM instruction table)。
gen_inst(Inst **ctp, Inst i)
¶この関数は命令 i
をコンパイルします。 vmgen-ex/peephole.c をご覧ください。
スーパー命令(superinstructions)を使用したくない場合(関数例の最後の 2 行のみの場合)は簡単ですが、
この例ではスーパー命令を使用可能なため、 少し複雑になります(see Peephole optimization)。
genarg_type_prefix(Inst **ctp, type type_prefix)
¶これにより、 (type-prefix
定義で定義されている) type の直接引数(immediate
argument)がコンパイルされます。 これらの関数を定義するのは簡単です(vmgen-ex/support.c を参照)。
あなたが直接引数(immediate argument)として使用するタイプごとに、 これらの関数のいずれかが必要です。
これらの関数を使用してコードを生成することに加えて、 スーパー命令(superinstructions)を使用したい場合は(または、 Vmgen
でサポートされているプロファイリングを使用したい場合は; ただし、 このサポートは主にスーパー命令の選択にも役立ちます)は、
すべての基本ブロック・エントリ・ポイントで BB_BOUNDARY
を呼び出す必要があります。 あなたが
BB_BOUNDARY
を利用する場合、 あなたは BB_BOUNDARY
も定義する必要があります(vmgen-ex/mini.y の定義をご覧ください)。
分岐の後に BB_BOUNDARY
を呼び出す必要はありません。 なぜなら、
あなたは途中に分岐を含むスーパー命令を定義することはないでしょう(もしあなたがそうして、 それがうまくいくなら、
分岐でスーパー命令で終了する理由はありません)、 そして、 分岐はプロファイラに自分自身をアナウンスするからです。
あなたがスーパー命令(superinstructions)を使用する場合にのみ、 のぞき穴最適化(peephole optimization)が必要です。 ただし、 スーパー命令を使用しない場合に、 のぞき穴最適化のコードを持っていても、 それほど差し障りはありません。
スーパー命令(superinstruction)の選択には、 単純な貪欲な覗き穴最適化(simple greedy peephole
optimization)アルゴリズムが使用されます: gen_inst
が VM 命令をコンパイルするたびに、 それを直前の VM
命令と結合(combine)できるかどうかがチェックされます(これは、以前ののぞき穴最適化から生じたスーパー命令である可能性もあります)。
結合できる場合、 現在の ‘*ctp’ に i
を配置する代わりに、 最後の命令を結合された命令に変更します。
のぞき穴最適化のコードは vmgen-ex/peephole.c にあります。 このファイルはほぼそのまま使用できます。 Vmgen は、 除き穴最適化のデータを含む file-peephole.i を生成します。
あなたは、 ‘vm_prim’ を初期化した後、 VM コードをコンパイルして覗き穴最適化のためのデータ構造を初期化する前に、
‘init_peeptable()’ を呼び出す必要があります。 その後、 VM コード生成関数を使用してコンパイルすると、 VM
命令がスーパー命令(superinstructions)に自動的に結合されます。 あなたが VM
分岐ターゲット間で命令を結合したくない時(そうしないと、 分岐先の適切な VM 命令が存在しなくなります)、 分岐ターゲットで
BB_BOUNDARY
(see VM code generation) を呼び出す必要があります。
VM コード逆アセンブラはインタープリター・システムではオプションですが、 フロント・エンドでのバグの検出(および VM インタープリターのバグと区別するため)に非常に役立つため、 開発およびメンテナンス中は強く推奨します。
Vmgen は、 file-disasm.i を生成することで VM コードの逆アセンブルをサポートします。 vmgen-ex/disasm.c と同様に、 このコードを関数にラップする必要があります。 このファイルはほぼそのまま使用できます。 ‘vm_A2B(a,b)’ や ‘vm_out’ や ‘printarg_type(value)’ に加えて、 上記で説明したように、 以下のマクロと変数が file-disasm.i で使用されます(あなたはこれらを定義する必要があります):
ip
この変数は、 現在の VM 命令のオペコードを指します。
IP IPTOS
¶‘IPTOS’ は現在の VM 命令の最初の引数であり、 ‘IP’ はそれを指します。 これはエンジンとまったく同じですが、 ここで ‘ip’ は VM 命令のオペコードを指します(エンジンとは対照的に、 ‘ip’ は次のセル、 またはさらにその 1 つ先のセルを指します)。
VM_IS_INST(Inst i, int n)
¶オペコード ‘i’ が VM 命令テーブルの ‘n’ 番目のエントリと同じかどうかをテストします。
VM プロファイラーは、 VM 命令シーケンスの実行カウントと出現カウントを取得するように設計されており、 これらのカウントはスーパー命令(superinstructions)としてシーケンスを選択するために使用できます。 VM プロファイラーは、 おそらくインタプリタ・システム(interpretive system)のプロファイリング・ツールとしては役に立ちません。 つまり、VM プロファイラーは開発者にとっては役立ちますが、 インタプリタ・システム(interpretive system)のユーザーにとっては役に立ちません。
プロファイラーの出力は次のとおりです: 各基本ブロック(少なくとも 1 回実行される)について、その基本ブロックとそのすべてのサブシーケンスの動的実行回数を出力します。 例:
9227465 lit storelocal 9227465 storelocal branch 9227465 lit storelocal branch
これは、 ‘lit storelocal branch’ で構成される基本ブロックは 9227465 回実行されるということを示しています。
この出力をさまざまな方法で組み合わせることができます。 たとえば、 vmgen-ex/stat.awk は、 動的実行(dynamic execution)や、静的発生(static occurence)や、 プログラムごとの発生を参照して、 指定のシーケンスの発生を合計します。 例:
2 16 36910041 loadlocal lit
これは、 シーケンス ‘loadlocal lit’ が 2 つのプログラムの 16 か所で発生し、 36910041 回実行されたことを示します。 これで、 スーパー命令(superinstructions)を任意の方法で選択できるようになりました(注意: コンパイルの時間制約と空間的制約により、 スーパー命令の数は通常 100 ~ 1000 に制限されることに注意してください)。 これを完了すると、 vmgen/seq2rule.awk によって、 上記形式行が Vmgen 入力ファイルにインクルードできるルールに変換されます。 このスクリプトではすべてのプレフィックスが定義されていることは保証されないため、 プレフィックスは他の方法で定義する必要があることに注意してください。 したがって、 プロファイルをスーパー命令(superinstructions)に変換するための全体的なスクリプトは以下のようになります:
awk -f stat.awk fib.prof test.prof| awk '$3>=10000'| #select sequences fgrep -v -f peephole-blacklist| #eliminate wrong instructions awk -f seq2rule.awk| #turn into superinstructions sort -k 3 >mini-super.vmg #sort sequences
ここでは、 動的カウント(dynamic count)がシーケンスの選択に使用されます(ただし、 暫定的な結果では、 静的カウントの方が良い結果が得られることを示しています)。 3 行目は、 スタックに直接アクセスするため、 スーパー命令内で発生してはいけない命令を含むシーケンスを削除します。 動的なカウント選択により、 より長いシーケンスのすべてのサブシーケンス(プレフィックスを含む)が確実に発生します(サブシーケンスは少なくとも長いシーケンスと同じカウントを持つため)。 最後の行でのソートにより、 プレフィックスの後ろで長いスーパー命令が確実に発生するようになります。
ただし、 これを使用する前に、 あなたにはプロファイラーが必要です。 Vmgen は file-profile.i を生成することでその作成をサポートします。 ほぼそのまま使用できるラッパー・ファイル vmgen-ex/profile.c も必要です。
プロファイラーは、すべての VM 制御フロー変更のターゲットを(実行中は SUPER_END
を通じて、 フロント・エンドでは
BB_BOUNDARY
を通じて)記録し、(SUPER_END
を通じて)ターゲットにされた変更の頻度をカウントすることによって機能します。 プログラムの実行後、 各 VM
基本ブロックが正しいカウントになるように数値が修正され(分岐を実行せずにブロックに入ってもカウントは増加しないという点を修正します)、
すべての基本ブロックのサブ・シーケンスが出力されます。 これらすべてを取得するには、 SUPER_END
(および
BB_BOUNDARY
) を適切に定義し、 file
にプロファイルを出力するときに
vm_print_profile(FILE *file)
を呼び出すだけです。
file-profile.i は逆アセンブラー・ファイルに似ており、vmgen-ex/profile.c
で定義された変数と関数に加えて、 VM 逆アセンブラー用すでに定義されている VM_IS_INST
を使用します(see VM disassembler)。
浮動小数点値をどのように扱うべきでしょうか? あなたは整数/ポインターと同一のスタックを使用するべきでしょうか、 それとも別のスタックを使用するべきでしょうか? このセクションでは、 実行速度の観点からこの問題について説明します。
より単純なアプローチは、 別の浮動小数点スタックを使用することです。 これにより、 整数/ポインターのサイズを考慮せずに FP
値のサイズを選択できるようになり、 多くのパフォーマンスの問題を回避できます。 主な欠点は、 これには FP
スタック・ポインターが必要であることです(そして、 これは 386 アーキテクチャーのレジスター・ファイルに収まらない可能性があり、
パフォーマンスが多少低下しますが、 他のオプションを考慮すると比較的わずかです)。 別の FP スタック(スタック・ポインタ fp
を使用)を使用する場合、 fpTOS の使用はほとんどのマシンで役に立ちますが、 一部のマシンでは fpTOS
レジスタがメモリに配置(spill)されるため、 fpTOS を使用すべきではありません。
もう一つのアプローチは、 整数/ポインターと浮動小数点値で 1 つのスタック(たとえば、sp
によってポイントされる)を共有することです。
spTOS
を使用しなくても、 これは問題ありません。 spTOS
を使用する場合、
コンパイラーはその変数を整数レジスターに入れるか浮動小数点レジスターに入れるかを決定する必要があり、
他の型の操作はほとんどのマシンで非常に高価になります(整数レジスターと FP レジスターの間で値を移動するため、 かなり高価です)。 一方の型の値を、
もう一方の型の 2 つの値(double
型)から合成する必要がある場合、 事態はさらに興味深いものになります。
この問題を回避する 1 つの方法は、Vmgen でサポートされている spTOS
を使用せず、 明示的なスタックのトップ変数(整数用に 1
つ、 FP 値用に 1 つ)を使用し、 一種の アキュムレータ+スタック・アーキテクチャを使用することです。 (たとえば、 Ocaml
バイト・コードはこのアプローチを使用します)。 ただし、 これは大きな変更であり、 その影響範囲は完全には明らかにはなっていません。
Vmgen の将来のバージョンについては、 作者達はいくつかのアイデアを持っています。 ただし、できることはたくさんありますので、 皆様からのフィードバックをお待ちしております。 あなたは Vmgen で何をしていますか? 不足している機能はありませんか?
私たちが考えているアイデアの 1 つは、 すべてのラッパー・ファイルをコピーして適応させる代わりに .c ファイルを 1 つだけ生成することです(型固有のマクロやスタック・ポインターなどをどこかで定義する必要があります)。 利点は、 バージョン間でラッパー・ファイルを変更した場合に、 あなたの変更とそれらに対する我々の変更を統合する必要がないことです。 これにより Vmgen は初心者にとっても使いやすくなるでしょう。 その主な欠点は、Vmgen の柔軟性が少し低下することです (柔軟性を好む人は、 現在ラッパー・ファイルに対して行っているように、 結果として得られる .c ファイルにパッチを適用することもできます)。 いずれにせよ、 生成された .c ファイルのアプローチで問題を引き起こすようなことをラッパー・ファイルに対して行っている場合は、 作者達までお知らせください。
ユーザーに関係する変更点 0.5.9-20020822 → 0.5.9-20020901:
ストアの最適化はデフォルトで無効になっていますが、 ユーザーが有効にすることができます(see Store Optimization)。 この最適化に関するドキュメントも新しくなりました。
ユーザーに関係する変更点 0.5.9-20010501 → 0.5.9-20020822:
今やマニュアルがあります(info, HTML, Postscriot, or plain text format)。
vmgen-ex サンプルのバリエーション vmgen-ex2 があります。 vmgen-ex2 では、 多くのキャストの代わりに共用体型(union)が使用されます。
この例の両方のバリエーションは、 ANSI C コンパイラでコンパイルできるようになりました(スイッチ・ディスパッチを使用するため、
パフォーマンスがかなり低下します)。 lcc
でテストしました。
Vmgen の gforth-0.5.9-20010501 バージョンのユーザーが現在のバージョンを使用するには、 あなたのソース・コード内のいくつかの点を変更する必要があります。 変更が完了するまで、 gforth-0.5.9-20010501 バージョンを保持しておくことをお勧めします(あなたは複数のバージョンの Gforth を同時にインストールできることに注意してください)。 今後は、 このような互換性のない変更は避けたいと思っています。
変更が必要な箇所:
TAIL;
¶は INST_TAIL;
に名前変更されました(偶然一致する可能性が低くなります)。
vm_A2B
¶いまや2つの引数を取ります。
vm_twoA2B(b,a1,a2);
¶vm_twoA2B(a1,a2,b) に変更されました(‘;’ が無いことに注意してください)。
また、 INST_ADDR
や LABEL
など、 いくつかの新しいマクロを定義する必要があります。
一部のマクロは新しいコンテキストで定義する必要があります。 たとえば、 VM_IS_INST
は逆アセンブラーでも必要になります。
バグを報告するには、 https://savannah.gnu.org/bugs/?func=addbug&group_id=2672 を使用してください。
Vmgen に関する議論(使用方法など) については、 メーリング・リスト bug-vmgen@mail.freesoftware.fsf.org を使用してください (購読には http://mail.gnu.org/mailman/listinfo/help-vmgen を使ってください)。
vmgen の情報は、 http://www.complang.tuwien.ac.at/anton/vmgen/ で見つけることができます。
Copyright © 2000,2001,2002 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
The purpose of this License is to make a manual, textbook, or other functional and useful document free in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others.
This License is a kind of “copyleft”, which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software.
We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference.
This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The “Document”, below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as “you”. You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law.
A “Modified Version” of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language.
A “Secondary Section” is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document’s overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them.
The “Invariant Sections” are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none.
The “Cover Texts” are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words.
A “Transparent” copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not “Transparent” is called “Opaque”.
Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modification. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only.
The “Title Page” means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, “Title Page” means the text near the most prominent appearance of the work’s title, preceding the beginning of the body of the text.
A section “Entitled XYZ” means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as “Acknowledgements”, “Dedications”, “Endorsements”, or “History”.) To “Preserve the Title” of such a section when you modify the Document means that it remains a section “Entitled XYZ” according to this definition.
The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License.
You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3.
You may also lend copies, under the same conditions stated above, and you may publicly display copies.
If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document’s license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects.
If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages.
If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public.
It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document.
You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version:
If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version’s license notice. These titles must be distinct from any other section titles.
You may add a section Entitled “Endorsements”, provided it contains nothing but endorsements of your Modified Version by various parties—for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard.
You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one.
The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version.
You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers.
The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work.
In the combination, you must combine any sections Entitled “History” in the various original documents, forming one section Entitled “History”; likewise combine any sections Entitled “Acknowledgements”, and any sections Entitled “Dedications”. You must delete all sections Entitled “Endorsements.”
You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects.
You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document.
A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an “aggregate” if the copyright resulting from the compilation is not used to limit the legal rights of the compilation’s users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document.
If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document’s Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate.
Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail.
If a section in the Document is Entitled “Acknowledgements”, “Dedications”, or “History”, the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title.
You may not copy, modify, sublicense, or distribute the Document except as expressly provided for under this License. Any other attempt to copy, modify, sublicense or distribute the Document is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See http://www.gnu.org/copyleft/.
Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License “or any later version” applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation.
To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page:
Copyright (C) year your name. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled ``GNU Free Documentation License''.
If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the “with...Texts.” line with this:
with the Invariant Sections being list their titles, with the Front-Cover Texts being list, and with the Back-Cover Texts being list.
If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation.
If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software.
Jump to: | -
\
#
A B C D E F G H I L M N O P R S T U V W |
---|
Jump to: | -
\
#
A B C D E F G H I L M N O P R S T U V W |
---|