2016-07-08 4 views
4

Angenommen, ich habe Pseudo-Code C wie unten:Kann Befehl Reihenfolge Cross Function Call?

int x = 0; 
int y = 0; 

int __attribute__ ((noinline)) func1(void) 
{ 
    int prev = x; (1) 

    x |= FLAG; (2) 

    return prev; (3) 
} 

int main(void) 
{ 
    int tmp; 

    ... 
    y = 5; (4) 
    compiler_mem_barrier(); 
    func1(); 
    compiler_mem_barrier(); 
    tmp = y; (5) 
    ... 
} 

Angenommen dies ein Single-Threaded-Prozess ist so brauchen wir nicht über Sperren zu kümmern. Und angenommen, der Code läuft auf einem x86-System. Nehmen wir an, der Compiler führt keine Neuordnung durch.

Ich verstehe, dass x86-Systeme nur Schreib-/Leseanweisungen neu ordnen können (Lesevorgänge können mit älteren Schreibvorgängen an verschiedenen Speicherorten neu angeordnet werden, aber nicht mit älteren Schreibvorgängen an denselben Speicherort). Aber es ist mir nicht klar , wenn Call/Ret-Anweisungen als WRITE/READ Anweisungen betrachtet werden. Also hier sind meine Fragen:

  1. Auf x86-Systemen, wird "Anruf" als WRITE-Anweisung behandelt? Ich nehme an, da call die Adresse an den Stack schickt. Aber ich habe kein offizielles Dokument gefunden, das das offiziell sagt. Also bitte hilf mit zu bestätigen.

  2. Aus dem gleichen Grund, wird "ret" als eine READ-Anweisung behandelt (da es die Adresse aus dem Stapel platzt)?

  3. Eigentlich kann "ret" Anweisung innerhalb der Funktion neu geordnet werden. Zum Beispiel, kann (3) vor (2) im folgenden ASM-Code ausgeführt werden? Das ergibt für mich keinen Sinn, aber "ret" ist keine Serialisierungsanweisung. Ich habe im Intel-Handbuch keinen Platz gefunden und gesagt, dass "ret" nicht nachbestellt werden kann.

  4. Im obigen Code kann (1) vor (4) ausgeführt werden? Vermutlich können Leseanweisungen (1) vor den Schreibanweisungen (4) neu geordnet werden. Die Anweisung "call" kann einen "jmp" -Teil haben, aber mit spekulativer Ausführung .... Ich denke, dass es passieren kann, aber ich hoffe, dass jemand, der mit diesem Thema vertrauter ist, dies bestätigen kann.

  5. Im obigen Code kann (5) vor (2) ausgeführt werden? Wenn "ret" als eine READ-Anweisung betrachtet wird, nehme ich an, dass dies nicht passieren kann. Aber ich hoffe, dass es jemand bestätigen kann.

Bei der Assembler-Code für func1() benötigt wird, sollte es so etwas wie sein:

mov %gs:0x24,%eax   (1)                                                 
orl $0x8,%gs:0x24   (2)                                                 
retq       (3) 

Bitte helfen. Vielen Dank!

+0

Fragen Sie sich: Kann die Funktion vom Compiler eingebunden werden? –

+1

Nehmen wir an, dass die Funktion nicht inline ist. Wenn die Funktion inline ist, weiß ich, was passieren kann. – yacc45

+0

Funktionen sind natürlich Compiler-Umordnungsbarrieren. Aber für den Prozessor, afaik, erzwingen weder 'call' noch 'ret' die Serialisierung. Siehe [Intel 64 und IA32 Architecture] (http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-manual-325462 .pdf) 8.3 Serialisierungsanweisungen. –

Antwort

4

Out-of-Order-Ausführung kann alles neu anordnen, aber es bewahrt die Illusion, dass Ihr Code in Programmreihenfolge ausgeführt wurde. Die Grundregel von OoOE ist, dass Sie keine Singlethread-Programme unterbrechen. Die Hardware verfolgt Abhängigkeiten, so dass die Anweisungen ausgeführt werden können, sobald ihre Eingaben und eine Ausführungseinheit bereit sind, bewahrt jedoch die Illusion, dass alles in der Programmreihenfolge passiert ist.


Sie scheinen OoOE mit der Bestellung auf einen einzigen Kern, in dem verwirrend zu sein, die Lasten/Läden zu anderen Kernen global sichtbar werden.

Wenn ein Thread den Stapelspeicher eines anderen Threads überwacht, der auf einem anderen Kern ausgeführt wird, dann wird der durch call generierte Speicher (durch Drücken einer Rücksprungadresse) zusammen mit anderen Speichern bestellt.

jedoch out-of-Order-Ausführung in dem Thread Ausführen dieses Codes ausführen kann tatsächlich call und ret Anweisungen, während ein Speicher auf einem Cache-Fehl verzögert wird, oder während einer langen Abhängigkeitskette ausführt. Mehrere Cache-Misses können gleichzeitig im Flug sein. Der Speicherreihenfolge-Puffer muss lediglich sicherstellen, dass spätere Speicher erst nach früheren Speichern tatsächlich global sichtbar werden, um die Speicherordnungssemantik von x86 beizubehalten.


Wenn Sie eine bestimmte Frage zu Hardware-Neuordnungs haben, sollten Sie wahrscheinlich asm-Code schreiben, C-Code nicht, weil C++ compilers can reorder at compile time based on the C++ memory model, das nicht ändert, wenn ein stark geordnete Ziel wie x86 kompiliert.

Siehe auch How does memory reordering help processors and compilers? (eine Java-Frage, aber meine Antwort ist nicht Java-spezifisch).


re: Ihr bearbeiten

Diese Antwort wurde bereits unter der Annahme, war Ihre Funktion noinline, und dass Sie über ASM sprechen, die wie Ihre C sahen, nicht das, was ein Compiler würde aus dem Code tatsächlich generiert .

mov %gs:0x24,%eax   (1)                                                 
orl $0x8,%gs:0x24   (2)                                                 
retq       (3) 

So x ist eigentlich in Thread-Local Storage, nicht ein reinen globalen int x. Für die Out-of-Order-Ausführung spielt das jedoch keine Rolle. Eine Last mit %gs Segment Override ist immer noch eine Last.

Verwandte Themen