Ist es legal, Ladenlokale / Konstruktion von flüchtigen Stack-Variablen zu optimieren?

8

Ich habe bemerkt, dass clang und gcc in einigen Szenarien die Konstruktion von oder die Zuordnung zu einem flüchtigen struct , das auf dem Stack deklariert ist, optimieren. Zum Beispiel der folgende Code:

%Vor%

Kompilieren zum Thema:

%Vor%

Auf der anderen Seite entfernt gcc die Speicher nicht, obwohl es die beiden impliziten Speicher zu einer einzigen optimiert:

%Vor%

Seltsamerweise wird clang einen flüchtigen Speicher nicht auf eine einzelne Variable int optimieren:

%Vor%

Kompiliert zu:

%Vor%

Darüber hinaus ist eine Struktur mit 1 Mitglied statt 2 nicht optimiert.

Obwohl gcc die Konstruktion in diesem speziellen Fall nicht entfernt, macht es vielleicht noch aggressivere Optimierungen für den Fall, dass die struct -Member selbst als volatile deklariert werden und nicht als struct selbst zum Zeitpunkt von Konstruktion:

%Vor%

kompiliert einfach zu einem einfachen ret .

Obwohl es durchaus "vernünftig" erscheint, diese Speicher zu entfernen, die durch vernünftige Prozesse kaum zu beobachten sind, hatte ich den Eindruck, dass in den standardmäßigen volatile -Lasten und -Speichern davon ausgegangen wird, dass sie Teil der -Obbildung sind Verhalten des Programms (zusätzlich zu Aufrufen von IO-Funktionen), Punkt. Die Implikation ist, dass sie nicht von "als ob" entfernt werden, da sie per definitionem das beobachtbare Verhalten des Programms verändern würden.

Habe ich damit unrecht oder klingelt es hier gegen die Regeln? Vielleicht ist Konstruktion von den Fällen ausgeschlossen, in denen von volatile mit Nebenwirkungen ausgegangen werden muss?

    
BeeOnRope 28.10.2017, 21:41
quelle

2 Antworten

2

Aus der Sicht des Standards ist es nicht erforderlich, dass Implementierungen irgendetwas darüber dokumentieren, wie Objekte physisch im Speicher gespeichert werden. Selbst wenn eine Implementierung das Verhalten der Verwendung von Zeigern des Typs unsigned char* für den Zugriff auf Objekte eines bestimmten Typs dokumentiert, könnte eine Implementierung Daten auf andere Weise physisch speichern und dann den Code für zeichenbasierte Lese- und Schreibvorgänge geeignet anpassen lassen .

Wenn eine Ausführungsplattform eine Beziehung zwischen den abstrakten Maschinenobjekten und dem von der CPU gesehenen Speicher angibt, und legt fest, auf welche Weise Zugriffe auf bestimmte CPU-Adressen Nebeneffekte auslösen können, von denen der Compiler keinen Qualitätscompiler kennt, der für niedrig geeignet ist - Die Programmierung auf dieser Plattform sollte Code generieren, bei dem das Verhalten von volatile -qualifizierten Objekten mit dieser Spezifikation übereinstimmt. Der Standard versucht nicht, alle Implementierungen für Low-Level-Programmierung (oder irgendeinen anderen bestimmten Zweck) geeignet zu machen.

Wenn die Adresse einer automatischen Variablen niemals externem Code ausgesetzt ist, muss ein volatile Qualifier nur zwei Effekte haben:

  1. Wenn setjmp in einer Funktion aufgerufen wird, muss ein Compiler alles Notwendige tun, um sicherzustellen, dass longjmp die Werte von volatile -qualifizierten Objekten nicht stört, selbst wenn sie zwischen den beiden geschrieben wurden setjmp und longjmp . Ohne das Qualifikationsmerkmal würde der Wert von Objekten, die zwischen setjmp und longjmp geschrieben wurden, unbestimmt werden, wenn ein longjmp ausgeführt wird.

  2. Regeln, die es einem Compiler erlauben würden anzunehmen, dass alle Schleifen, die keine Nebenwirkungen haben, vollständig ausgeführt werden, gelten nicht in Fällen, in denen auf ein flüchtiges Objekt innerhalb der Schleife zugegriffen wird, unabhängig davon, ob eine Implementierung dies definieren würde jedes Mittel, durch das ein solcher Zugang beobachtbar wäre.

Außer in diesen Fällen würde die as-if-Regel es einem Compiler ermöglichen, das Qualifikationsmerkmal volatile in der abstrakten Maschine auf eine Weise zu implementieren, die keine Beziehung zur physischen Maschine hat.

    
supercat 31.10.2017, 19:22
quelle
3

Lassen Sie uns untersuchen, was der Standard direkt sagt. Das Verhalten von volatile wird durch ein Paar Anweisungen definiert. [intro.execution] / 7:

  

Die geringsten Anforderungen an eine konforme Implementierung sind:

     
  • Zugriffe durch flüchtige glvalues ​​werden streng nach den Regeln der abstrakten Maschine ausgewertet.
  •   

...

Und [intro.execution] / 14:

  

Das Lesen eines Objekts, das durch einen flüchtigen glvalue (6.10) gekennzeichnet ist, das Ändern eines Objekts, das Aufrufen einer Bibliotheks-E / A-Funktion oder das Aufrufen einer Funktion, die eine dieser Operationen ausführt, sind alle Nebeneffekte, die Änderungen im Status der Ausführungsumgebung.

Nun, [intro.execution] / 14 trifft nicht zu, da nichts im obigen Code das "Lesen eines Objekts" darstellt. Du initialisierst es und zerstörst es; es wird nie gelesen.

Das geht also [intro.execution] / 7. Der hier wichtige Ausdruck ist "greift auf bis flüchtige glvalues" zu. Während temp sicherlich ein volatile Wert ist, und es ist sicherlich ein glvalue ... Sie greifen nie wirklich durch. Ach ja, Sie initialisieren das Objekt, aber das greift nicht auf "obwohl" temp als glvalue zu.

Das heißt, temp als Ausdruck ist ein glvalue, gemäß der Definition von glvalue: "Ein Ausdruck, dessen Auswertung die Identität eines Objekts, eines Bitfeldes oder einer Funktion bestimmt." Die Anweisung erstellt und initialisiert temp Ergebnisse in einem glvalue, aber die Initialisierung von temp greift nicht über einen glvalue zu.

Denken Sie an volatile like const . Die Regeln für const -Objekte gelten erst, nachdem nach initialisiert wurde. In ähnlicher Weise gelten die Regeln für volatile -Objekte erst nach der Initialisierung.

Es gibt also einen Unterschied zwischen volatile nonvol2 temp = {1, 2}; und volatile nonvol2 temp; temp.a = 1; temp.b = 2; . Und Clang macht sicherlich in diesem Fall das Richtige .

Dies besagt, dass die Inkonsistenz von Clang in Bezug auf dieses Verhalten (die Optimierung nur bei Verwendung einer Struktur und nur bei Verwendung einer Struktur, die mehr als ein Element enthält) darauf hindeutet, dass dies wahrscheinlich keine formale Optimierung durch die Schriftsteller von Clang. Das heißt, sie nutzen den Wortlaut nicht so sehr aus, da dies nur eine merkwürdige Eigenart eines zufälligen Codes ist, der zusammenkommt.

  

Obwohl gcc die Konstruktion in diesem speziellen Fall nicht entfernt, macht es vielleicht noch aggressivere Optimierungen für den Fall, dass die struct -Member selbst als volatile deklariert sind, anstatt die struct selbst am Punkt von Konstruktion:

Das Verhalten von GCC ist hier:

  1. Nicht im Einklang mit dem Standard, da es gegen [intro.execution] / 7, aber
  2. verstößt
  3. Es gibt absolut keine Möglichkeit zu beweisen , dass es nicht dem Standard entspricht.

Angesichts des Codes, den Sie geschrieben haben, gibt es einfach keine Möglichkeit für einen Benutzer zu erkennen, ob diese Lese- und Schreibvorgänge tatsächlich stattfinden. Und ich vermute eher, dass in dem Moment, in dem Sie alles tun, damit die Außenwelt es sehen kann, diese Änderungen plötzlich im kompilierten Code erscheinen. Wie sehr auch immer der Standard es "beobachtbares Verhalten" nennen möchte, Tatsache ist, dass durch das C ++ eigene Speichermodell niemand es sehen kann .

GCC kommt wegen des Mangels an Zeugen mit dem Verbrechen davon. Oder zumindest glaubwürdige Zeugen (wer es sehen könnte, würde UB anrufen).

Du solltest also volatile nicht wie einen Optimierungsschalter behandeln.

    
Nicol Bolas 28.10.2017 21:50
quelle