A Use-Definition-Kette ( UD-Kette ) ist eine Datenstruktur, die aus einer Verwendung U einer Variablen und allen Definitionen D dieser Variablen besteht das kann diese Verwendung ohne weitere eingreifende Definitionen erreichen. Eine Definition kann viele Formen haben, aber im Allgemeinen wird die Zuweisung eines bestimmten Werts zu einer Variablen verstanden (was sich von der Verwendung des Begriffs unterscheidet, der sich auf das Sprachkonstrukt bezieht, das einen Datentyp umfasst und Speicher zuordnet).
Ein Gegenstück zu einer UD-Kette ist eine Definition-Use-Kette ( DU-Kette ), die aus einer Definition, D, einer Variablen und besteht alle Verwendungen, U, die aus dieser Definition ohne weitere dazwischenliegende Definitionen erreichbar sind.
Sowohl UD- als auch DU-Ketten werden unter Verwendung einer Form statischer Code-Analyse erstellt, die als Datenflussanalyse bezeichnet wird. Die Kenntnis der Use-Def- und Def-Use-Ketten für ein Programm oder Unterprogramm ist eine Voraussetzung für viele Compiler-Optimierungen, einschließlich der ständigen Propagierung und der Eliminierung allgemeiner Teilausdrücke.
Zweck [ edit ]
Das Erstellen der Use-Define- oder Define-Use-Ketten ist ein Schritt in der Aktivitätsanalyse, sodass logische Repräsentationen aller Variablen identifiziert und durchlaufen werden können der Code.
Betrachten Sie den folgenden Code-Ausschnitt:
int x = 0 ; / * A * /
x = x x 19659019] ] / * B * /
/ * 1, einige Verwendungen von x * /
x = 35 ; / C * /
/ * 2, einige weitere Verwendungen von x * /
Beachten Sie, dass x
an drei Punkten (mit A, B und C gekennzeichnet) ein Wert zugewiesen wird. An der mit "1" markierten Stelle sollte die use-def-Kette für x
jedoch anzeigen, dass ihr aktueller Wert von Zeile B stammen muss (und ihr Wert in Zeile B muss von Zeile A stammen). Im Gegensatz dazu zeigt die Use-Def-Kette für x
an der mit "2" markierten Stelle an, dass ihr aktueller Wert von der Linie C stammen muss. Seit dem Wert von x
in Block 2 hängt nicht von Definitionen in Block 1 oder früher ab, x
könnte dort auch eine andere Variable sein; praktisch ist eine andere Variable - nennen Sie es x2
.
int x = 0 ; / * A * /
x = x x 19659019] ]; / * B * /
/ * 1, einige Verwendungen von x * /
int x2 = 35 / * C * /
/ * 2, einige Verwendungen von x2 * /
Das Aufteilen von x
in zwei separate Variablen wird als Live-Range-Aufteilung bezeichnet. Siehe auch statisches Formular für Einzelzuweisungen.
Die Liste der Anweisungen bestimmt eine starke Reihenfolge unter den Anweisungen.
- Anweisungen werden mit den folgenden Konventionen gekennzeichnet: wobei i ist eine ganze Zahl in ; und n ist die Anzahl der Aussagen im Grundblock
- Variablen sind kursiv gekennzeichnet (z. B. v u und ) t )
- Es wird angenommen, dass jede Variable eine Definition im Kontext oder Bereich hat. (In statischen Einzelzuweisungsformen sind use-define-Ketten explizit, da jede Kette ein einzelnes Element enthält.)
Für eine Variable wie v wird ihre Deklaration als V bezeichnet ] (kursiver Großbuchstabe), und kurz gesagt, wird seine Deklaration als . Im Allgemeinen kann sich eine Deklaration einer Variablen in einem äußeren Bereich befinden (z. B. eine globale Variable ).
Definition einer Variablen [ edit ]
Wenn eine Variable v auf der LHS einer Zuordnungserklärung steht, wie dann ist eine Definition von v . Jede Variable ( v ) hat mindestens eine Definition in ihrer Deklaration ( V ) (oder Initialisierung).
Verwendung einer Variablen [ edit ]
Wenn Variable, v steht auf der RHS-Anweisung es gibt eine Aussage, mit i [ j und dass es sich um eine Definition von v handelt und sie bei (oder kurz gesagt, wenn eine Variable v rechts von einer Anweisung dann v hat eine Verwendung in der Anweisung ).
Hinrichtung [ edit ]
Betrachten Sie die sequentielle Ausführung der Liste der Anweisungen und was jetzt als Berechnung in der Aussage beobachtet werden kann, j :
Ausführungsbeispiel für def-use-chain [ edit ]
Dieses Beispiel basiert auf einem Java-Algorithmus zum Auffinden der gcd. (Es ist nicht wichtig zu verstehen, was diese Funktion bewirkt.)
1 / **
2 * @am (a, b) die zur Berechnung des Divisors verwendeten Werte
3 * @return den größten gemeinsamen Teiler von a und b zurück
4 ** /
5 int gcd ( int a ; int b b {
6 int c = a ;
7 int d = 19659019]
8 wenn ( c == 0 )
9 d ; 10 während ( d ! = 0 ) {
11 d )
12 c = c d
13
; d = d - c ;
15 ]
16
186] return c ;
17 }
Um alle Def-Use-Ketten für Variable d zu ermitteln, führen Sie die folgenden Schritte aus:
- Zum ersten Mal wird die Variable definiert (Schreibzugriff).
- In diesem Fall handelt es sich um "
d = b
" (l.7)
- In diesem Fall handelt es sich um "
- Zum ersten Mal wird die Variable gelesen.
- In diesem Fall handelt es sich um "
return d
"
- In diesem Fall handelt es sich um "
- Schreiben Sie diese Informationen in folgendem Stil auf: [name of the variable you are creating a def-use-chain for, the concrete write access, the concrete read access]
- In diesem Fall gilt Folgendes:
[d, d=b, return d]
- In diesem Fall gilt Folgendes:
Wiederholen Sie diese Schritte auf folgende Weise: Kombinieren Sie jeden Schreibzugriff mit jedem Lesezugriff (jedoch NICHT umgekehrt).
Das Ergebnis sollte lauten:
1 [ d d = b d d d = b während ( d 0 0 )]
3 [ d d = b d )]
4 d d = b ; 19659188] c - d ]
5 [ d [196590129] d = 19659188] 19659188] d = d - c
6 d d = 19659249] - c während ( d ! = 0 ]
7 7 d d [19659012] d [19659249] = d - c wenn ( c d 8 8 8 ] 8 ] (19659012)] d d = d - c c c c d ]
9 d d d d 19659188] ] d = d - c ]
Sie müssen darauf achten, ob die Variable zeitlich geändert wird.
Zum Beispiel: Von Zeile 7 bis Zeile 13 im Quellcode wird d nicht neu definiert / geändert.
In Zeile 14, d konnte eine Neudefinition vorgenommen werden, weshalb Sie diesen Schreibzugriff am d mit allen möglichen Lesezugriffen, die erreicht werden könnten, rekombinieren müssen.
In diesem Fall ist nur der Code nach Zeile 10 relevant. Linie 7 kann zum Beispiel nicht mehr erreicht werden. Für Ihr Verständnis können Sie sich zwei verschiedene Variablen vorstellen d :
19659244] 1 d1 d1 = b d.1 ] d1 d1 = b während ( d1 ! ! )]
3 d1 d1 = b wenn d1 )]
4 d1 d1 = b ; 19659188] c - d1 ]
5 d1 d1 = 19659249 d1 = d1 - c ]
6 d2 d2 d2 . 19659249] - c während ( d2 ! = 0 ]
7 7 196590 12] d2 = d2 - c wenn c c d2 )
8 d2 d2 = d2 c c - 19659188] d2
9 [196599188] d2 d2 = = = = 19659189] ] c d2 = d2 - c ]
Als Ergebnis könnten Sie so etwas erhalten. Die Variable d1 würde durch b ersetzt.
1 / **
2 * @am (a, b) die zur Berechnung des Divisors verwendeten Werte
3 * @return den größten gemeinsamen Teiler von a und b zurück
4 ** /
5 int gcd ( int a ; int b b {
6 int c = a ;
7 int d ; ] wenn ( c == 0 )
9 return b 10 wenn ; ( b ! = 0 ) {
11 if ( c b b b {
12 c = c b ;
13 [196599210] d 19659019]
14 }
15 else
16 d = b ] - c ;
17 während ( d ! = 0 18 18 if ( c > d )
19 c = c - d d d ; 19659196] 20 else
21 d = d - c 22
19659586]
}
24 return c ;
25 }
Methode zum Aufbau eines use-def (oder ud ) chain [ edit ]
- Satzdefinitionen in Anweisung
- Für jedes i in finden Sie Live-Definitionen, die in den Anweisungen
- Verbindung zwischen Definitionen und Verwendungen herstellen
- Setzen Sie die Aussage als Definitionsaussage
- Töte vorherige Definitionen
Mit diesem Algorithmus werden zwei Dinge erreicht:
- Für die Verwendung und Definitionen der Variablen wird ein gerichteter azyklischer Graph (DAG) erstellt. Das DAG spezifiziert eine Datenabhängigkeit zwischen Zuweisungsanweisungen sowie eine Teilreihenfolge (daher Parallelität zwischen Anweisungen).
- When-Anweisung erreicht ist, gibt es eine Liste von live variablen Zuweisungen. Ist nur eine Zuweisung live, könnte beispielsweise eine konstante Ausbreitung verwendet werden.
No comments:
Post a Comment