Technische Informatik I Übung 3: Assembler Roman Trüb Computer Engineering Group, ETH Zürich 1
Lernziele Übung 3 Aufgabe 1 Aufbau und Aufruf von Funktionen in Assembler Assembler Codeanalyse Aufgabe 2 Aufbau und Manipulation des Stacks Schreiben einer vollständigen Assembler-Funktion 2
Voraussetzungen Übung 3 Grundlegend Assembler-Kenntnisse Vorlesung Kapitel 2 Computerübung 2 Funktionen und Stack Vorlesung Kapitel 3 (v.a. Folien 3-10 bis 3-21) Hennessy Appendix A, A.6 3
Stack wächst nach unten Der Stack [3-14] Adresse 0xFFFFFFFF Sichern von Kontext im Speicher: Argumente, Registerinhalten und lokale Variablen Aufgrund verschachtelter Funktionsaufrufe keine absolute Adresse im Speicher Stackframe main() Stackframe Func1() Stackframe Func2() Stackframe Func3() freier Stack Adresse 0x0 4
Stack wächst nach unten Stack bei Funktionsaufruf $fp Gesicherte Argumente Rücksprungadresse Framepointer Stackframe der aufrufenden Funktion $sp lokale Daten 5
Funktionsaufruf: Sichern der Register [3-15] Aufrufendes Programm Vor Funktionsaufruf temporäre Register sichern (wenn nicht als Parameter genutzt) Unterprogramm Am Anfang der Funktion sichere Register sichern (wenn in Funktion geändert) Sichere Register $s0-$s7 $sp, $fp Temporäre Register $t0-$t9 $a0-$a3 $ra $v0, $v1 $gp 6
Ablauf eines Funktionsaufrufs [3-15] Aufrufendes Programm Unterprogramm 1. Temporäre Register sichern 2. Argumente speichern 3. Sprunginstruktion (jal) 4. Rahmen allozieren ($sp) 5. Sichere Register speichern 6. Rahmenzeiger $fp nachführen 11. Temporäre Register laden 12. Zeiger $sp zurücksetzen (2.) 7. Resultate sichern 8. Sichere Register laden 9. Rahmen de-allozieren ($sp) 10. Zurückspringen (jr $ra) 7
Funktionsaufruf mit Sprunginstruktionen Funktionsaufruf jal Label2 # $ra=pc+4 & go to Label2 Zurückspringen (am Ende der Funktion) jr $ra # PC=$ra (Adresse der nächsten Instruktion ist in Register $ra) 8
Übungsaufgaben 9
Aufgabe 1: Wurzelfunktion Implementierung des Heron Verfahrens Eingabe: $f12 = Radikand a $a1 = Anzahl Iterationen n max Iteration: x n+1 = 1 x 2 n + a x n Ausgabe: $f0 = Ergebnis x nmax Keine Stackverwaltung Sicherung der Register vereinfacht 10
Aufgabe 1: Implementierung 2 heron: move $f20, $f12 Sichern der Register $ra und $f20 Label4: jr $ra Wiederherstellen der Register Register werden vor Verwendung gesichert, um sie am Ende der Funktion wieder in den Originalzustand zu versetzen 11
Lösung zu Aufgabe 1 12
Lösung 1: Implementierung 1 heron: move $f2, $f12 # $f2 := $f12 move $t1, $zero # $t1 := 0 ble $a1, $zero, $Label3 # if ($a1 <= $0) then goto $Label3 li $f4, 5.0e-1 # $f4 := 0.5 $Label5: div $f0, $f12, $f2 # $f0 := $f12 / $f2 add $f0, $f2, $f0 # $f0 := $f0 + $f2 mul $f0, $f0, $f4 # $f0 := $f0 * $f4 addi??? $t1, $t1, 1 # ==> Hier # oder: fehlt addi eine Zeile $a1, $a1, -1 slt $t0, $t1, $a1 # if ($t1 < $a1) then $t0 := 1 # else $t0 := 0 move $f2, $f0 # $f2 := $f0 bne $t0, $zero, $Label5 # if ($t0!= $zero) then goto $Label5 $Label3: move $f0, $f2 # $f0 := $f2 jr $ra # Jump and return ($pc := $ra) Initialisierung x n+1 = 1 2 x n + a x n Schleifenzähler while ($t1 < n) Finalisierung 13
Lösung 1: Implementierung 2 (main) void main(void) { float sqrt2 = heron(2, 10); main: li $f12, 2.0e0 li $a1, 10 jal heron 14
Lösung 1: Implementierung 2 (heron) float heron(float a, int n) { // Sichern der Register if (n <= 0) { return a; else { float prev_res = heron(a, n-1); return (0.5*(prev_res + a/prev_res)); // Wiederherstellen der Register heron: move $f20, $f12 bgt $a1, $0, $Label2 move $f0, $f20 j $Label4 $Label2: move $f12, $f20 addi $a1, $a1, -1 jal heron div $f2, $f20, $f0 add $f2, $f2, $f0 li $f0, 5.0e-1 mul $f2, $f2, $f0 move $f0, $f2 $Label4: jr $ra Reg. a1 f0 f2 f12 f20 Variable n result a a 15
Lösung 1: Implementierung 2 (heron) float heron(float a, int n) { // Sichern der Register // Wiederherstellen der Register heron: move $f20, $f12 bgt $a1, $0, $Label2 move $f0, $f20 j $Label4 $Label2: move $f12, $f20 addi $a1, $a1, -1 jal heron div $f2, $f20, $f0 add $f2, $f2, $f0 li $f0, 5.0e-1 mul $f2, $f2, $f0 move $f0, $f2 $Label4: jr $ra Reg. a1 f0 f2 f12 f20 Variable n result a a 16
Lösung 1: Implementierung 2 (heron) float heron(float a, int n) { // Sichern der Register if (n <= 0) { else { // Wiederherstellen der Register heron: move $f20, $f12 bgt $a1, $0, $Label2 move $f0, $f20 j $Label4 $Label2: move $f12, $f20 addi $a1, $a1, -1 jal heron div $f2, $f20, $f0 add $f2, $f2, $f0 li $f0, 5.0e-1 mul $f2, $f2, $f0 move $f0, $f2 $Label4: jr $ra Reg. a1 f0 f2 f12 f20 Variable n result a a 17
Lösung 1: Implementierung 2 (heron) float heron(float a, int n) { // Sichern der Register if (n <= 0) { return a; else { // Wiederherstellen der Register heron: move $f20, $f12 bgt $a1, $0, $Label2 move $f0, $f20 j $Label4 $Label2: move $f12, $f20 addi $a1, $a1, -1 jal heron div $f2, $f20, $f0 add $f2, $f2, $f0 li $f0, 5.0e-1 mul $f2, $f2, $f0 move $f0, $f2 $Label4: jr $ra Reg. a1 f0 f2 f12 f20 Variable n result a a 18
Lösung 1: Implementierung 2 (heron) float heron(float a, int n) { // Sichern der Register if (n <= 0) { return a; else { float prev_res = heron(a, n-1); // Wiederherstellen der Register heron: move $f20, $f12 bgt $a1, $0, $Label2 move $f0, $f20 j $Label4 $Label2: move $f12, $f20 addi $a1, $a1, -1 jal heron div $f2, $f20, $f0 add $f2, $f2, $f0 li $f0, 5.0e-1 mul $f2, $f2, $f0 move $f0, $f2 $Label4: jr $ra Reg. a1 f0 f2 f12 f20 Variable n result/ prev_res a a 19
Lösung 1: Implementierung 2 (heron) float heron(float a, int n) { // Sichern der Register if (n <= 0) { return a; else { float prev_res = heron(a, n-1); return (0.5*(prev_res + a/prev_res)); // Wiederherstellen der Register heron: move $f20, $f12 bgt $a1, $0, $Label2 move $f0, $f20 j $Label4 $Label2: move $f12, $f20 addi $a1, $a1, -1 jal heron div $f2, $f20, $f0 add $f2, $f2, $f0 li $f0, 5.0e-1 mul $f2, $f2, $f0 move $f0, $f2 $Label4: jr $ra Reg. a1 f0 f2 f12 f20 Variable n result prev_res temp a a 20
Lösung 1: Effizienz Implementierung 1: Iteration (Schleifen) Implementierung 2: Rekursion Schleifen sind schneller: Aufbau des Stacks weniger aufwändig Weniger Instruktionen (Register effektiver ausgenutzt) 21
Aufgabe 2: Fibonacci-Zahlen Fib n = Fib n 1 + Fib n 2 Fib 1 = Fib 2 = 1 22
Aufgabe 2: Register nach Funktionsaufrufen li $s3, 3 # $s3 = 3 li $t2, 2 # $t2 = 2 li $a1, 1 # $a1 = 1 li $v0, 0 # $v0 = 0 jal somefunction # $s3 == # $t2 == # $a1 == # $v0 == 3 [nicht definiert] [nicht definiert] [evtl. Rückgabewert von somefunction, ansonsten nicht definiert] 23
Stack wächst nach unten Aufgabe 2: Stack am Funktionsanfang $fp Gesicherte Argumente Rücksprungadresse Framepointer Stackframe der aufrufenden Funktion $sp lokale Daten 24
Lösung Aufgabe 2 25
Lösung 2: Pseudocode int Fib(int n) { int result; if (n <= 2) { result = 1; else { int n1 = Fib(n-1); int n2 = Fib(n-2); result = n1 + n2; return result; Fib: # Vorbereitungsphase: sw $ra, 4($sp) bgt,, recursion j abbauphase rekursion: jal Fib jal Fib abbauphase: j $ra 26
Lösung 2: Registerzuordnung int Fib(int n) { int result; if (n <= 2) { result = 1; else { int n1 = Fib(n-1); int n2 = Fib(n-2); result = n1 + n2; return result; Variable n result n1 n2 Zu sichern? ja nein ja nein Temporäre Register für Zwischenergebnisse verwenden 27
Lösung 2: Vorbereitungsphase Fib: addi $sp, $sp, -12 # Schritt 4: Stack Pointer herabsetzen sw $a0, 8($sp) # Schritt 5: # Das Argument von Fib im Stack sichern sw $ra, 4($sp) # Schritt 5: ra sichern sw $fp, 0($sp) # 5: Alten Frame Pointer auf Stack retten addi $fp, $sp, 8 # Schritt 6: Neuen Frame Pointer setzen # (zeigt auf den Rahmenanfang & # in diesem Fall auf das Argument) move $t0, $a0 # Das Argument von Fib in t0 laden li $t1, 2 bgt $t0, $t1, rekursion # if n > 2 then goto $recursion li $t0, 1 # else fib(2) = fib(1) = 1 j abbauphase # Zur Abbauphase springen 28
Stack wächst nach unten Lösung 2: Stack nach Vorbereitungsphase Gesicherte Argumente Rücksprungadresse Framepointer lokale Daten Stackframe der aufrufenden Funktion $fp $sp n altes $ra altes $fp Argument der Funktion zu sichernde Register Push $ra: addi $sp, $sp, -4 sw $ra, 0($sp) Pop $ra: lw $ra, 0($sp) addi $sp, $sp, 4 29
Lösung 2: Aufruf von Fib(n-1) move $t0, $a0 # Das Argument n von Fib in t0 laden rekursion: addi $sp, $sp, -4 # Stack Pointer herabsetzen. Wir wollen # einen weiteren Wert speichern addi $a0, $t0, -1 # $t0 = n-1 jal Fib # Fib(n-1) sw $v0, 0($sp) # Resultat von Fib(n-1) auf dem # Stack speichern 30
Stack wächst nach unten Lösung 2: Stack nach Aufruf von Fib(n-1) Gesicherte Argumente Rücksprungadresse Framepointer lokale Daten Stackframe der aufrufenden Funktion $fp $sp n altes $ra altes $fp Fib(n-1) Argument der Funktion gesicherte Register Resultat von Fib(n-1) Resultat vorläufig auf Stack belassen 31
Lösung 2: Aufruf von Fib(n-2) rekursion: addi $sp, $sp, -4 # Stack Pointer herabsetzen addi $a0, $t0, -1 # $t0 = n-1 jal Fib # Fib(n-1) sw $v0, 0($sp) # Resultat von Fib(n-1) speichern lw $t0, 0($fp) # Argument von Fib wieder holen # t0 = n addi $a0, $t0, -2 # Argument fuer Fib(n-2) # a0 = n-2 jal Fib # Fib(n-2) 32
Lösung 2: Rekursion rekursion: addi $sp, $sp, -4 # Stack Pointer herabsetzen addi $a0, $t0, -1 # $t0 = n-1 jal Fib # Fib(n-1) sw $v0, 0($sp) # Resultat von Fib(n-1) speichern lw $t0, 0($fp) # Argument von Fib wieder holen addi $a0, $t0, -2 # Argument fuer Fib(n-2) jal Fib # Fib(n-2) move $t0, $v0 # Resultat von Fib(n-2) aus v0 # holen und in t0 laden lw $t1, 4($sp) # Resultat von Fib(n-1) vom Stack # holen und in t1 laden add $t0, $t0, $t1 # t0 = Fib(n-2) + Fib(n-1) addi $sp, $sp, 4 # Stack Pointer heraufsetzen 33
Stack wächst nach unten Lösung 2: Stack vor Abbauphase Gesicherte Argumente Rücksprungadresse Framepointer lokale Daten Stackframe der aufrufenden Funktion $fp $sp n altes $ra altes $fp Argument der Funktion gesicherte Register 34
Lösung 2: Abbauphase abbauphase: move $v0, $t0 # Im Register t0 liegt das Resultat # Schritt 7: Resultat sichern lw $fp, 0($sp) # Schritt 8: Alten Frame Pointer holen # und gleich in fp laden lw $ra, 4($sp) # 8: Return Adresse vom Stack holen addi $sp, $sp, 12 # Schritt 9: Stack Pointer heraufsetzen jr $ra # Schritt 10: Ruecksprung 35
Stack wächst nach unten Lösung 2: Stack nach Abbauphase $fp Gesicherte Argumente Rücksprungadresse Framepointer Stackframe der aufrufenden Funktion $sp lokale Daten 36
Lösung 2: Abschliessende Bemerkungen Auch hier iterative Implementierung möglich: int Fib(int n) { int i1 = 1, i2 = 1, tmp; while(n-- > 2) { tmp = i1+i2; i2 = i1; i1 = tmp; return i1; Iteratives Verfahren deutlich effizienter Immer hinterfragen: Problem wirklich rekursiv? 37