スーパースカラースタックプロセッサ:連続クロッシングスネークとハリネズミ



この記事の続きでは、スタックされたマシンのフロントエンドが OoOを背後に持つスーパースカラープロセッサを隠すことを実証できまし

この記事のトピックは、関数呼び出しです。



スタックと関数呼び出し



一般的な理由により、説明されているアーキテクチャでは、関数の呼び出しに問題があると想定されています。 実際、関数から戻った後、現在の関数のコンテキストでレジスタの状態を期待して返します。 最新のレジスタアーキテクチャでは、レジスタこのために2つのカテゴリに分けられます-呼び出し側は一部の安全を担当し、呼び出し側は他の人を担当します。



しかし、このアーキテクチャでは、フロントエンドはスタックであるため、コンパイラーはレジスターの存在を認識しない場合があります。 また、プロセッサ自体がコンテキストの保存/復元を処理する必要がありますが、これは重要なタスクのようです。



しかし、最初に、スタック自体のトピックについて余談します。

スタックの概念そのものが誤解を招く可能性があります。



IBM / 360にはハードウェアスタックはありません。 ただし、関数を(再帰的にも含めて)呼び出すことができます。このため、パラメーターはメモリ領域に格納されます。これは、呼び出しの前にOSから要求する必要があります。



x86にはハードウェアスタックがありますが、このアーキテクチャをスタックに割り当てる人はいません。 このスタックは、ローカル変数と関数パラメーターを保存するための優れたメカニズムです。



AMD29K、SPARC、およびItaniumは、いわゆるバークレーリスクアーキテクチャファミリに属し、それらのスタックには別の重要な機能があります。レジスタプールは、スタックの最上部(レジスタウィンドウ)であり、関数呼び出し時のパラメータ転送を高速化することになっています。

SPARC V7は、AMD29Kよりも数年早く登場しましたが、(著者にとっては)アーキテクチャ的にスリムではないようです。

ItaniumのRSEユニットは、一般的にAMD29Kのユニットと似ていますが、後に登場しました。



AMD29Kは親切な言葉に値する



2つのハードウェアスタックがあります。 アーキテクチャのいくつかのスタックは新しいものではなく、ソ連の(そして現在の )エルブラスのバロウズB5000にありました。 ただし、2番目のスタックは、プロシージャからの戻りアドレスを格納するように設計されています。 ここでは、両方ともデータの保存に使用されます。

ここで注目すべき面白いアイデアは何ですか?

  1. 各関数のレジスタの番号付けは独自です。これはバークレーRISCの機能です
  2. ただし、スタック分割はこの特定のアーキテクチャの機能です。 SPARCでは、レジスタウィンドウ通常の(高速ではない)変数と同じスタックに保存されます。 そして、塗りつぶし/流出はブレークで行われます-各ウィンドウはそのフレームからです。


スタックを「大きいが遅い」と「小さいが速い」に分けることは非常に重要です。 動機に対処しましょう。



パラメーターの受け渡し、関数の呼び出し



スタックをローカル変数(およびパラメーター)のリポジトリとしてのアイデアは、その論理と完全性において美しいです。 弱点-システムのパフォーマンスは、レイテンシとメモリのパフォーマンスに依存します。 PDP-11の時代には何もできませんでしたが、それ以来状況は変わりました。



まず、レジスタへのアクセスはメモリへのアクセスよりも大幅に高速になり、データのキャッシュが必要になりました。 第二に、はるかに多くのレジスタを持つことが可能になりました。



多数のレジスタを所有すると、それらを使用して関数を呼び出すときに引数の転送を高速化することができます。 実際、通常、パラメーターはほとんどなく(ローカルデータよりも少ない)、その値はほとんどの場合、誰かが必要とします。 そして、どのローカルデータがレジスタに入るのに値するのかによって、最適化が決定されます。 もちろん、これは非常に粗雑な単純化であり、一般的な動機付けを示すためだけに設計されています。



現在、レジスタを介してパラメーターを渡す2つの方法が一般的です。

  1. 特定のレジスタに特別な役割を割り当てます。 たとえば、 MSVC(x86-64)では、最初の4つの整数引数はレジスタRCX、RDX、R8、R9を介して渡されます。 これから、すべての機能に対してレジスタの番号が統一されます。 この手法を使用するアーキテクチャには、MIPS、PPC、ARM、DEC Alphaも含まれます。コールチェーンには、スタック以外にパラメータを保存する場所がないことは明らかです。 これはすべてキャッシュに関するものです。 または、この関数の特定のパラメーターはもう使用されておらず、保存する必要がないと判断できるオプティマイザーに対して。
  2. 登録ウィンドウのテクニック。 このアーキテクチャのブランチは、Berkeley Riscプロジェクトから発展しました。 これには、i960、Itanium、SPARCだけでなく、すでに分解したAMD29Kも含まれます。 一番下の行は、限られた数のパラメーターと呼び出された関数のローカルデータがレジスタウィンドウにあり、次の関数が呼び出されるとウィンドウがシフトするため、このデータがスタックを形成します。 各関数には、独自のレジスタ番号があります。 ウィンドウに収まらないものはすべて通常のスタックに分類され、一時データ用のグローバルレジスタも使用できます。 したがって、 i960およびSPARCの場合、レジスタスタックは通常のスタックに散在していますが、AMD29KおよびItaniumの場合、これらは異なるスタックです。 実際、AMD29KとItaniumは、コンパイラが「高速スタック」にふさわしいと考えるデータの種類を選択するためのコンパイラを提供します。他のすべては自動的に行われます。 これは、Cで廃止されたキーワード「レジスタ」を連想させるものですが、決定はコンパイラによって行われますが、高レベル言語です。


潜在的なパフォーマンスの観点では、両方のアプローチはほぼ同等です。 最初のアプローチでは、最適化の全体的な負担はコンパイラにあり、プロセッサにはありません。これにより、(おそらく)最終システムの設計がより簡単で安価になります。



しかし、少し夢中になったので、設計中のアーキテクチャで現在の関数のコンテキストを維持することに戻りましょう。



関数コンテキストの保存



そして、このコンテキストには何が含まれていますか? 子プロシージャの呼び出し時に占有されていたレジスタ。

この場合、計算が不十分な式のレジスタはトポロジカルソートを介して相互接続されますが、これは呼び出された関数には関係ありません。 モップによる出力レジスターのキャプチャー時に、キャプチャーされた順番は関係ありません。



注目に値するニュアンスがあります-関数呼び出しの開始前に、その引数が計算されたすべてのモップがうまくいくはずです。 したがって、プロセッサの観点から見ると、関数呼び出しは、任意の数の引数を持つ一般化された命令です。



ここで、レジスタの番号付けを決定する価値があります。

番号付けを一般的にする、つまり SPARCではなくMIPSパスをたどりました。



ウィンドウを登録してみましょう。



ただし、関数呼び出しの外側に注目することで、これらすべてが内部で実行される方法を見失っています。 著者は自分自身をハードウェアの専門家とは考えていませんが、それでも問題を予測しています。

幸いなことに、 次の記事でそれらに対処します。



All Articles