Und noch ein Quiz
Annahme:
$s1 = 0xFFFFFFFF
$s2 = 0x00000001
In welchem der beiden Code‐Abschnitte wird gesprungen?
...
slt $t0,$s1,$s2
bne $t0,$zero, lab ...
...
lab: ...
...
...
sltu $t0,$s1,$s2 beq $t0,$zero, lab ...
...
lab: ...
Sprung: ...
ja nein
Sprung:
ja nein
Prozeduren
Das Prinzip von Prozeduren
Hauptprogramm:
. . .
x = 2*fakultät(10) .
. . Programm‐
abarbeitung
Prozedur mit dem Namen fakultät
. .
Berechne n!
. . Prozeduraufruf
mit Parameter n=10
Prozedurrücksprung mit Ergebnis n!
Randbemerkung: was ist n! ?
Programmzähler und Rücksprungadresse
0x0040000 : 0011 ... 1001 0x0040004 : 0001 ... 1000 0x0040008 : 1001 ... 1111 0x004000c : 1011 ... 0001 0x0040010 : 0011 ... 1000 0x0040014 : 1001 ... 1111 0x0040018 : 0001 ... 0001 0x004001c : 1011 ... 0011 0x0040020 : 1011 ... 1100 0x0040024 : 0101 ... 1001 0x0040028 : 1000 ... 0011 0x004002c : 1000 ... 1011 0x0040030 : 0001 ... 1100 0x0040034 : 1001 ... 1111 0x0040038 : 1001 ... 1111
Startadresse des Hauptprogramms Aufruf der Prozedur
Register $pc
Prozedur Fakultät
Rücksprung aus der Prozedur
Adresse Maschineninstruktion
Register $ra
Assembler‐Beispiel
Hauptprogramm: ...
0x004000c addi $a0,$zero,10 # setze $a0 auf 10 0x0040010 jal Fakultaet # rufe Prozedur auf 0x0040014 sll $v0,2 # Berechne Rückgabe*2
...
Fakultaet:
# Die Prozedur Fakultaet
# erwartet den Übergabeparameter in $a0
# gibt das Ergebnis in $v0 zurück
0x0040024 ... # Berechnung der Fakultät ... # Das Ergebnis sei in $a0 0x004002c add $v0,$a0,$zero # speichere Ergebnis in $v0 0x0040030 jr $ra
Register $pc Register $ra Register $a0 Register $v0
Problem
Hauptprogramm:
. .
$s0 = 42 vor Aufruf
. .
x = 2*fakultät(10) .
.
Annahme immer noch $s0=42 !?!
. . Programm‐
abarbeitung
Prozedur mit dem Namen fakultät
. .
Berechne n!
Überschreibe dabei
$s0 mit 114 .
. Prozeduraufruf
mit Parameter n=10
Prozedurrücksprung mit Ergebnis n!
Register $s0
Lösung
Hauptprogramm:
. .
$s0 = 42 vor Aufruf
.
x = 2*fakultät(10) .
.
Es gilt immer noch $s0=42 !!!
. .
Prozedur mit dem Namen fakultät
Rette Inhalt von $s0 auf dem Stack
.
Berechne n!
($s0 wird dabei überschrieben) Restauriere Inhalt von $s0 mittels Stack
. Register $s0 .
0x7fffffff : ...
0x7ffffffe : ...
0x7ffffffd : ...
0x7ffffffc : ...
0x7ffffffb : ...
0x7ffffffa : ...
0x7ffffff9 : ...
0x7ffffff8 : ...
0x7ffffff7 : ...
0x7ffffff6 : ...
0x7ffffff5 : ...
0x7ffffff4 : ...
0x7ffffff3 : 0x7ffffff2 : 0x7ffffff1 : 0x7ffffff0 : 0x7fffffef : 0x7fffffee : 0x7fffffec :
.
Register $sp
Stack
Assembler‐Beispiel
Fakultaet: addi $sp, $sp, -4 # erhöhe Stack-Pointer um ein Word sw $s0, 0($sp) # rette $s0 auf Stack
# berechne Fakultät
# $s0 wird dabei überschrieben
lw $s0, 0($sp) # restauriere $s0 vom Stack addi $sp, $sp, 4 # dekrementiere Stack-Pointer jr $ra # Springe zurück zum Aufrufer
...
0x7ffffff7 : ...
0x7ffffff6 : ...
0x7ffffff5 : ...
0x7ffffff4 : ...
0x7ffffff3 : 0x7ffffff2 : 0x7ffffff1 : 0x7ffffff0 : 0x7fffffef : 0x7fffffee : 0x7fffffec :
...
Register $s0 Register $sp
(sei $s0=0xffef2314 vor Aufruf von Fakultaet)
Registerkonvention
Name Nummer Verwendung Wird über Aufrufgrenzen bewahrt?
$zero 0 Konstante 0 n.a.
$at 1 nein
$v0‐$v1 2‐3 Prozedur‐Rückgabe nein
$a0‐$a3 4‐7 Prozedur‐Parameter nein
$t0‐$t7 8‐15 Temporäre nein
$s0‐$s7 16‐23 Temporär gesicherte ja
$t8‐$t9 24‐25 Temporäre nein
$k0‐$k1 26‐27 nein
$gp 28 ja
$sp 29 Stack‐Pointer ja
$fp 30 ja
$ra 31 Return‐Adresse ja
Rekursive Prozeduren
Hauptprogramm:
. . .
x = 2*fakultät(10) .
. .
Prozedur mit dem Namen fakultät
. .
Berechne n!
. . Prozeduraufruf
Letzter Rücksprung mit Gesamtergebnis
Wenn Rekursionsende noch nicht erreicht, dann erneuter Prozeduraufruf
(„mit kleinerem Parameter“)
Alle vorigen Rücksprünge
Verwendung des Stacks
Haupt‐
programm
Fakultät
Fakultät
Fakultät
Rekursionsende Stack
$sp
Fakultät
Assembler‐Beispiel
Auf der nächste Folie wollen wir die Fakultät n! nach folgendem Algorithmus berechnen
int fact (int n) { if (n < 1) {
return 1;
}
else {
return n * fact(n-1);
} }
Assembler‐Beispiel
# Berechnung der Fakultät von n (also n!)
# Eingabeparameter n ist in $a0 gespeichert
# Rückgabeparameter der Berechnung ist $v0
fact: addi $sp, $sp, -8 # push Stack-Pointer um zwei Word sw $ra, 4($sp) # rette Rücksprungadresse auf Stack sw $a0, 0($sp) # rette Eingabeparameter auf Stack slti $t0, $a0, 1 # teste n < 1
beq $t0, $zero, L1 # wenn n >= 1 dann gehe nach L1 addi $v0, $zero, 1 # es wird 1 zurückgegeben
addi $sp, $sp, 8 # pop Stack-Pointer um zwei Word jr $ra # Rücksprung zum Prozedur-Aufrufer L1: addi $a0, $a0, -1 # setze Argument auf n-1
jal fact # rufe fact rekursiv mit n-1 auf
lw $a0, 0($sp) # restauriere Eingabeparam vom Stack lw $ra, 4($sp) # restauriere Rücksprungadr vom Stack addi $sp, $sp, 8 # pop Stack-Pointer um zwei Word
Procedure‐Frame und Frame‐Pointer
Null bis vier Argument‐
Register ($a0‐$a3) Return‐Adresse $ra Null bis acht Saved‐
Register ($s0‐$s7) Möglicher zusätzlicher
Speicher der während der Ausführung der
Prozedur benötigt wird und nach Prozedurrückkehr nicht
mehr
Hohe Adresse
Niedrige Adresse Stack‐Pointer $sp
Frame‐Pointer $fp
Unbenutzer Stack‐Speicher
Benutzer Stack‐Speicher
Procedure‐Frame
Argument 5 Argument 6
Speicherbelegungskonvention
Reserviert
Text (d.h. das Programm in Form von Maschinen‐
instruktionen) Statische Daten (z.B.
Konstanten oder statische Variablen)
Stack
Der Heap speichert alle dynamischen (d.h.
während der Laufzeit angelegten) Daten.
Heap
0x00400000 0x10000000 0x10008000 0x7ffffffc
0x00000000
$pc
$sp
$gp
Die Sprunginstruktionen zusammengefasst
Instruktion Beispiel Beduetung
j j Label $pc = Sprungadresse
jal jal Label $ra = $pc+4, $pc = Sprungadresse jr jr $s1 $pc = Registerinhalt
$pc ist der Program‐Counter
$ra ist das 32te CPU‐Register
Schwieriges Quiz
Rmul: addi $sp, $sp, -4 # rette Rücksprungadresse sw $ra, 0($sp) #
add $t0, $a0, $zero # $t0 = n addi $a1, $a1, -1 # m = m - 1
beq $a1, $zero, End # wenn m=0, dann Ende jal Rmul # $v0 = Rmul(n,m-1) add $t0, $t0, $v0 # $t0 = $t0 + $v0 End: add $v0, $t0, $zero # $v0 = $t0
lw $ra, 0($sp) # springe zurück addi $sp, $sp, 4 #
Rekursive Berechnung von n*m in der Form Rmul(n,m) = n+Rmul(n,m‐1) Eingabeparameter: $a0 für n und $a1 für m>0
Rückgabeparameter: $v0 Temporäre Berechnung: $t0
$a0, $a1, $v0, $t0 brauchen nach Registerkonvention alle nicht über Aufrufgrenzen bewahrt zu werden.
Registerkonvention, dass ein Register über Aufrufgrenzen nicht bewahrt wird bedeutet:
• Register darf nach belieben überschreiben werden.
• Register muss vor dem Rücksprung nicht restauriert werden.
• Prozedur muss aber das Register für sich selbst sichern!
• Beispiel:
Verwendung von $t0 Sichern von $t0
Aufruf einer anderen Prozedur Restaurieren von $t0
•Ausnahme: wir wissen genau, dass das Register in keiner der aufgerufenen Prozeduren verwendet wird.
• Prozeduren, die keine anderen aufruft muss natürlich temporäre Register nie sichern.
Prozedur, die keine andere aufruft nennt man auch Leaf‐Procedure
Bemerkung zu vorigem Quiz
System‐Call – Mechanismus zum Aufrufen von Betriebssystem‐
funktionen.
Anwendung der Instruktion syscall am Beispiel:
addi $v0, $zero, 1 # Lade System‐Call‐Code in $v0
# hier System‐Call‐Code 1 für den
# Systemcall print_int addi $a0, $zero, 42 # Lade das Argument für den
# System‐Call nach $a0.
# Hier soll die Zahl 42
# ausgegeben werden.
syscall # Rufe den System‐Call auf
# dies führt zu einer Exception
# vom Typ 8 (System‐Call)
System‐Calls
In SPIM/MARS verfügbare System‐Calls
Pseudoinstruktionen, Direktiven und Makros
Motivation für Pseudoinstruktionen
Wir hatten häufiger schon
addi $s1,$zero,wert # $s1=wert
Eine Instruktion li (Load‐Immediate) wäre doch nachvollziebarer li $s1,wert # $s1=wert
MIPS als ISA aus dem RISC‐Lager versucht aber den Instruktion‐Set möglichst klein zu halten. Damit ist so was wie ein li in der ISA nicht eingebaut. Kann man ja mit einem addi und dem $zero
Register ausdrücken.
Dennoch gibt es in MIPS oben genannte Instruktion. Wo kommt die her?
Das ist eine sogenannte Pseudoinstruktion, die der Assembler in Instruktionen der MIPS ISA übersetzt.
Umsetzung von Pseudoinstruktionen
Wie würde folgende move Instruktion vom Assembler umgesetzt?
move $s1,$s2 # Pseudoinstruktion $s1=$s2
Wie würde folgende blt Instruktion vom Assembler umgesetzt?
blt $s1,$s2, Label # Springe nach Label,
# wenn $s1<$s2 gilt
Beachte: Registerkonvention. Pseudoinstruktionen die ein Register zum Zwischenspeichern von Ergebnissen brauchen, benutzen dazu das Register $at (Assembler‐Temporary)
Einige MIPS‐Assembler Pseudoinstruktioen
Instruktion Beispiel Erklärung des Beispiel
blt, bltu blt $s1, $s2, Label Springe nach Label, wenn
$s1 < $s2 (signed)
bgt, bgtu bgt $s1, $s2, Label Springe nach Label, wenn
$s1 > $s2 (signed)
ble, bleu ble $s1, $s2, Label Springe nach Label, wenn
$s1 <= $s2 (signed)
bge, bgeu bge $s1, $s2, Label Springe nach Label, wenn
$s1 >= $s2 (signed)
li li $s1, 42 Lade Immediate 42 in $s1 move move $s1, $s2 $s1 = $s2
MARS unterstützt beispielsweise neben den 155 Basisinstruktionen weitere 388 zusätzliche Pseudoinstruktionen.
Direktiven
Direktiven vereinfachen das Datenlayout eines Programms im Speicher.
Damit der Assembler ein Programm, wie auf der rechten Seite gezeigt, erzeugt, schreiben wir:
.text 0x00400010 li $v0, 1
li $a0, 5 syscall
.data 0x10001000 str:
.asciiz "Hallo Welt!“
0x00400010 : li $v0, 1 0x00400014 : li $a0, 5 0x00400014 : syscall ...
0x10001000 : ‘H’
0x10001001 : ‘a’
0x10001002 : ‘l’
0x10001003 : ‘l’
0x10001004 : ‘o’
...
Makros (nicht Besandteil von SPIM/MARS)
Makros definieren in einem Wort eine Folge von Instruktionen.
Beim assemblieren wird jedes Auftreten des Makronamens im Code mit den Instruktionen ausgetauscht.
Beispiel:
.macro print_int($arg) la $a0, int_str
mov $a1, $arg jal printf
.end_macro ...
.data
int_str: .asciiz „%d“
Code:
...
print_int($t0) ...
wird expandiert zu:
...
la $a0, int_str mov $a1, $t0
jal printf ...