Register Stack Engine

Die Register Stack Engine (RSE) ist ein programmiertechnischer Mechanismus zur effizienten Handhabung des Stapelspeichers (Stack) bei der IA-64, der Intel-Architektur für 64-Bit-Prozessoren.

Die Parameter einer Funktion werden in Registern übergeben, von denen ein Teil wie ein Stack arbeitet. Der logische Stack setzt sich aus den Registern und einem Teil des Hauptspeichers zusammen, so dass er beliebig groß werden kann (so groß wie der Hauptspeicher) und gerade aktuelle Stackframes (Speicherseiten) schnell für Berechnungen in der ALU verfügbar sind (so schnell wie die Register). Die RSE ist ein Hardwaremechanismus, der durch einige Maschinenbefehle kontrolliert wird und abhängig vom Betriebsmodus sowie der Implementierung des Prozessors unterschiedlich stark selbständig arbeitet, das heißt die Register mit dem Speicher mit freier Bandbreite unabhängig vom ausgeführten Befehlsstrom, quasi im Hintergrund, synchronisiert.

Registeraufbau

Die IA-64-Architektur definiert 128 frei verwendbare Register (im Gegensatz zu acht Registern bei der IA-32), von denen die Register 0 bis 31 wie normale statische Register funktionieren (acht bei der IA-32). Die Register 32 bis 127 unterstützen spezielle Mechanismen, die beim Aufruf von Unterprogrammen und Funktionen das sonst notwendige langwierige Sichern- und Rücksichern von Registerinhalten auf dem Stack (im Hauptspeicher) reduzieren oder ganz vermeiden.

Funktionsaufruf

Bei einem Funktionsaufruf werden die Funktionsargumente, Rücksprungadresse und Rückgabewerte grundsätzlich nicht auf dem Stack übergeben, sondern in Registern. Die Register 32 bis 127 dienen dabei selbst als Stack, der Register-Stack-Pointer zeigt auf das Register, das als nächstes für die Übergabe von Funktionsparametern zur Verfügung steht. Sind keine Register mehr verfügbar, werden solche aus einem früheren Stackrahmen in den Hauptspeicher (meistens nur in den Prozessorcache) geschrieben.

Bei jedem Funktionsaufruf werden die Register rotiert, sodass die Funktionsparameter immer beginnend bei Register 32 zu finden sind. Nach diesen folgen die lokalen Daten der Funktion und dann die Register, die für die Weitergabe an untergeordnete Funktionen bestimmt sind. Man spricht von Input-, Local- und Output-Registern, die über Kürzel (in, loc und out) bequem angesprochen werden können. Die Länge dieser drei Bereiche wird während der Kompilierung festgelegt.

Technisch gesehen werden die Register bei der Rotation nicht kopiert, sondern lediglich umbenannt (Register Mapping). Dazu wird ein Zeiger angepasst, der auf das jeweils gültige Register 32 zeigt.

Struktur der Register

Die folgende Darstellung dient der Erklärung der Funktionsweise und kann vom Programm nicht beeinflusst werden.

Die 96 dynamischen Register sind in 4 Partitionen geteilt:

Die ersten zwei Partitionen enthalten Register, die für Stackrahmen von übergeordneten Funktionen verwendet werden.

  • clean: das Register ist mit dem Hauptspeicher synchronisiert
  • dirty: das Register ist noch nicht mit dem Hauptspeicher synchronisiert
  • current: das Register gehört zum aktuellen Stackrahmen
  • invalid: das Register enthält keine wichtigen Daten und kann für neue Funktionsaufrufe, also untergeordnete Funktionen, benutzt werden

Da das aktuelle input Register 0 immer die Nummer 32 hat, sieht das Registerfile etwa so aus:

32 (logisch)   →   | current (input|local|output) | invalid | clean | dirty|   ←   127

Ist der alte Stackrahmen beispielsweise 32–36, dann muss der Zeiger auf den Beginn der current-Partition 4 Register nach rechts verschoben werden, da der alte Stackrahmen aber nicht an den Registerpositionen 2832 gespeichert wird, ist er dann an den Positionen 123–127 zu finden, wie bei einem Ringpuffer.

Tatsächlich kann sich das aktuelle Register 32 aber ein beliebiges Register sein, das heißt tatsächlich kann die obige Darstellung so gespeichert sein:

32 („physisch“)   →   | clean | dirty | current | invalid |   ←   127

Wenn die invalid-Partition für den nächsten Funktionsaufruf zu klein ist, werden Register aus clean zu invalid hinzugefügt. Wenn invalid und clean zu klein sind, müssen Register aus dirty synchronisiert werden, bevor der Funktionsaufruf erfolgen kann. Bei der automatischen Synchronisation wird wenn möglich die dirty- zugunsten der clean-Partition verkleinert, das heißt Register, die die Daten früherer Stackrahmen enthalten, werden mit dem Hauptspeicher synchronisiert, denn clean-Register können sofort neu verwendet werden. Oder die invalid-Partition wird zugunsten der clean-Partition verkleinert, das heißt Daten aus früheren Stackrahmen werden aus dem Hauptspeicher geladen, um bei der Rückkehr in eine übergeordnete Funktion wieder verfügbar zu sein. Kurz gesagt, die clean-Partition sollte besonders groß sein.

Bei einem Taskwechsel müssen daher auch nicht 127 Register gesichert werden, sondern nur die Register der dirty-Partition sowie die statischen Register 0 bis 32.

Synchronisation

Die Synchronisation mit dem Hauptspeicher ist nur nötig, wenn keine Register mehr verfügbar sind. Das wäre zum Beispiel der Fall bei einer Aufruftiefe von 96 Funktionen, die keine Parameter übernehmen (pro Funktionsaufruf muss nur die Rücksprungadresse gesichert werden). Optional kann die Synchronisation mit dem Hauptspeicher auch im Hintergrund geschehen. Falls die Load/Store-Einheit nicht beschäftigt ist, kann sie spekulativ entweder die Anzahl der verfügbaren Register erhöhen, indem sie Register von früheren Aufrufen in den Hauptspeicher schreibt, oder Register früherer Stackrahmen aus dem Hauptspeicher auslesen, damit sie bei der Rückkehr aus Funktionen schnell verfügbar sind. Falls die Synchronisation spekulativ erfolgt, spricht man vom eager mode, sonst vom lazy mode. Die spekulative Synchronisation kann ein- und ausgeschaltet werden, sofern sie implementiert ist.

Vergleich mit anderen Technologien

Die Register Stack Engine ist eine Generalisierung der Register Windows, wie sie bei SPARC-Prozessoren auftauchen. Dort ist die Größe der Register-Fenster immer gleich, während sie bei der Register Stack Engine beliebig festgelegt werden kann.

Literatur

  • Intel Itanium Architecture Software Developer’s Manual, Volume 2 Kapitel 6