5

私は C のような言語からスタック マシンまでおもちゃのコンパイラを構築していますが、関数をどう処理し、ローカル変数をブロックするかを理解する必要があるところまで来ています。抽象的に考えると、スペクトルの両端に 2 つのオプションがあるように見えます。1) 各変数のスタック領域を前処理して事前に割り当てる、2) VM に特別な命令を追加してスタックをウォークします。

各変数のスタック領域を前処理して事前に割り当てる

これには、事前に変数のすべてのアドレスが提供されるという利点があるため、非常に賢くする必要も、スタックをウォークするために VM に追加の命令を追加する必要もありません。欠点は、決して実行されずに一連の変数全体を宣言する条件付きコードが多くの不要なスペースを占有するため、非常に無駄になる可能性があることです。例えば、

a : t1 = value;
if (test) {
  b : t2; c : t3; d : t4; ...;
}

上記のコードでは、testが常に false であっても、条件分岐内のすべての変数にスペースを割り当てます。

VM に特別な命令を追加して、スタックをウォークする

私が思いついたもう 1 つのアプローチは、変数宣言ごとにコードを生成し、実行時にそれらの変数のアドレスを把握するための特別な VM 命令を追加することでした。これにより、無駄なスタック スペースの問題は解決されますが、計算オーバーヘッドが追加されます。これは、いくつかのキャッシュ方法で解決できる可能性があります。

では、正しいアプローチは何ですか?私が考えていなかった別のアプローチの方が優れていますか?

4

1 に答える 1

7

スタック マシンの考え方は、オペランド スタックで計算を行うことです。すべてをスタックに格納する必要があるわけではありません。これはよくある誤解です。通常、ローカルの vaiables / ブロック スコープのアクセスは登録操作にマップされます。

.NET CLR と Java の両方に、「ローカル」変数やその他の種類の変数を保存およびフェッチする命令があります。単純な変数アクセスのためにスタックを歩きたくないので、それに倣うことをお勧めします。それは非常に非効率的です。レジスタのように、変数をロード/ストアするのは効率的です。ほとんどのスタック マシンには、まだランダム アクセス ストレージがあります。

CLR では、各メソッドの開始時にすべてのローカル変数を事前に割り当てます。事前に割り当てる変数は、明示的な高レベル変数とコンパイラによって生成された一時変数が混在している場合があります。しかし、それらがスタック上になければならないということは何もありません。私が取り組んできた VM では、連想配列やベクターのような構造のような高速アクセス領域に VM を実装しました。ildasm を使用して .NET メソッドを逆アセンブルし、ローカル変数がどのように宣言され、処理されるかに注意することをお勧めします。

例:

 total = apples + oranges

マップ先:

 ldloc 'apples'   # load a local onto stack
 ldloc 'oranges'  # load a local onto stack
 add              # add 2 operands on stack
 stloc 'total'    # store local from stack

以前の回答では、スタック ベースのマシンについて説明し、レジスタ マシンと比較しました。その中に有益な情報があることを願っています。https://stackoverflow.com/a/24301283/257090

単純な Dictionary または HashTable を使用して、stfld (ストア フィールド) と ldfld (ロード フィールド) のプロトタイプを実装するのはかなり簡単です。後で、アセンブルまたはランタイムを最適化して、シンボリックな名前ベースの参照を整数参照にコンパイルできます。これは、特に名前による変数の実行時ルックアップが必要ない場合に当てはまります。ただし、リフレクション API の場合は、追加のメタデータを実装して、数値アドレスまたはポインター アドレスを元の名前に相互参照する必要があります。

PS: 私があなたの質問を誤解した場合、またはさらに議論したい場合は、コメントしてください。

于 2014-07-19T04:04:09.883 に答える