Wie wird das Überspringen von Anweisungen während Prozeduraufrufen in Pipeline-Architekturen vermieden?

Ich habe eine Frage zu einem PC-Register (IP in x86-Jargon). In den meisten Architekturen wird es während einer Ausführungsphase aktualisiert und speichert somit eine Adresse eines nächsten abzurufenden Befehls. Es schien mir klar, bis ich anfing, über eine Pipeline-Architektur nachzudenken. Stellen Sie sich zum Beispiel die folgende klassische RISC-Pipeline mit 5 Stufen vor (Abrufen, Decodieren, Ausführen, Speicherzugriff, Zurückschreiben), die mit den folgenden Anweisungen gefüllt ist (bezeichnet als „a“, „b“, „c“, „d“ ):

F D E M W
c b a - -

0x12 call [0x100] ; a
0x14 mov ax, 10   ; b
0x16 add ax, 2    ; c
0x18 nop          ; d <- IP

Zu dem Zeitpunkt, zu dem ein Befehl "a" die Stufe E (Ausführen) in der Pipeline erreicht, ist sie bereits voll mit nachfolgenden Befehlen und IP zeigt auf einen Befehl an der Adresse 0x18 (der nächste, der abgerufen werden soll). Wenn "call [0x100]" ausgeführt wird, speichert es den Inhalt der IP (eine Rücksendeadresse) auf dem Stack. Aber es ist offensichtlich nicht die Adresse der Anweisung nach "call [0x100]"! Wenn wir also vom CALL zurückkehren, überspringen wir effektiv 2 Anweisungen, da die Pipeline während der CALL-Ausführung geleert wird!

Was bedeutet, dass:

  • Es gibt ein weiteres verstecktes Register, das die Adresse der ausgeführten Anweisungen speichert und anstelle von IP auf dem Stapel gespeichert wird
  • So geht das nicht
  • mir fehlt was :)
Suchen Sie für einen Teil der Antwort nach "MIPS Branch Delay Slot". (von späteren Designern allgemein als wirklich schlechte Idee angesehen ...)

Antworten (1)

Durchlesen des Referenzhandbuchs zur X86-Baugruppe ... https://courses.cs.washington.edu/courses/cse548/05wi/files/x86-reference-long.pdf

Sie finden Folgendes: „Die call-Anweisung ruft nahe Prozeduren mit einem vollständigen Zeiger auf. call bewirkt, dass die im Operanden genannte Prozedur ausgeführt wird. Wenn die aufgerufene Prozedur abgeschlossen ist, wird der Ausführungsfluss bei der Anweisung fortgesetzt, die der call-Anweisung folgt (siehe die Rücksendeanweisung)"

Dies sagt Ihnen, dass der Anruf zuerst zurückkommen muss. Dies bedeutet, dass bei der Ausführung noop-Anweisungen danach platziert werden, bis es zurückkehrt.

Unmittelbar nachdem dies aufgerufen wurde, bestimmt der Steuerblock, dass es sich um einen Anruf handelt, was ein Befehl ist, der eine Aktion durch die Gefahrenerkennungseinheit erfordert. Diese Einheit löst im Wesentlichen eine Anrufunterbrechung aus. Unmittelbar nach dem Sehen dieser Anweisung tritt dieser Interrupt auf, um noops aufzurufen, und lässt den PC nicht inkrementieren.

Was Sie beschrieben haben, ist mir klar, aber meine Frage ist anders. Sofern keine Pipeline beteiligt ist, ist der Mechanismus zum Sichern/Wiederherstellen des Programmzählers unkompliziert - wenn CALL ausgeführt wird, wird die Adresse des nächsten Befehls auf den Stapelstapel geschoben und bei der Rückkehr in den Programmzähler eingefügt. Ich bin jedoch gespannt, wie das Gleiche in Pipeline-Architekturen gemacht wird, da der Programmzähler mehrere Anweisungen voraus zeigt, wenn CALL die Execute-Phase erreicht. Wie damit umgegangen wird, interessiert mich.
@raiks Gotcha. Du überdenkst es :) Ich werde es bearbeiten.
Sie sagen also im Grunde, dass eine frühe CALL-Erkennung dazu führt, dass eine CPU Blasen in die Pipeline einfügt, wodurch verhindert wird, dass ein Programmzähler inkrementiert wird, bis CALL ausgeführt wird, richtig? Dieses Schema wäre in meinem Beispiel nur realisierbar, wenn die Erkennung in der allerersten Stufe (Fetch) erfolgt wäre. Dann würde ein Programmzähler immer noch auf die Anweisung nach CALL zeigen und seinen Wert bis zu dem Punkt beibehalten, an dem CALL die Ausführungsstufe erreicht. Wenn ich Sie richtig verstanden habe, ist es interessant, wie die Früherkennung vor der Dekodierungsstufe erreicht wird, insbesondere in sehr langen Pipelines, in denen jede Stufe sehr spezialisiert ist.
@raiks Genau. Diese Früherkennung ist entscheidend, sobald wir in eine Welt der Peripheriegeräte eintreten und über den Prozessor hinausblicken. Der Prozessor muss in der Lage sein, an jedem Punkt unterbrochen und in Noops versetzt zu werden. Der Anruf hat einen Platz in der ISR-Tabelle, der es ihm ermöglicht, diesem Früherkennungsschema zu folgen. MIPS macht dasselbe mit syscall.
@raiks Um die sehr langen spezialisierten Prozessoren zu kommentieren, ist dies ähnlich wie ein größeres System aufgeteilt. Sie wählen aus, was wo unterbrochen werden muss, und in einigen Architekturen finden Sie Startsignale, die mit dem nächsten Register verriegeln müssen, aber nach demselben Prinzip gelöscht werden.
Richtig interessant wird es bei Interrupts. Während die Früherkennungstechnik für Software-Interrupts (INT xx) funktionieren sollte, die im Wesentlichen nur ein Spezialfall von Unterprogrammaufrufen sind, funktioniert sie nicht für Hardware-Interrupts. Wenn ein Hardware-Interrupt ausgelöst wird, ist die Pipeline der CPU bereits mit Anweisungen gefüllt und es gibt keine Möglichkeit, ihn frühzeitig zu erkennen und Blasen einzufügen, die die Inkrementierung des Programmzählers verhindern, wie wir zuvor besprochen haben.
Die einzige Lösung, die ich mir dafür vorstellen kann, besteht darin, die Größe der Anweisungen in der Pipeline herauszufinden und den Programmzähler "zurückzuspulen" (indem diese Größe vom Wert des aktuellen Programmzählers subtrahiert wird). Dann kann es auf den Stapel geschoben und später von einem ISR wiederhergestellt werden. Allerdings ... Der einfachste Weg, um mit allen besprochenen Fällen umzugehen, wäre, einfach ein weiteres Register zu haben, um eine Adresse eines aktuell ausgeführten Befehls zu speichern. Soweit ich weiß, geschieht dies beispielsweise in MIPS, aber wir diskutieren x86, bei dem die Dinge anders zu sein scheinen.
@raiks Bei externen Interrupts wird nämlich der aktuelle Zustand aller Register gespeichert. Sie speichern sie in der richtigen Registerbank, sodass Stack, Heap und die einfachen Register verwendet werden können, wohin Sie auch gegangen sind. Wenn wir zurückkehren, sind der aktuelle Zustand und alle Informationen sicher.