(:requiretuid:)

Einführung in TGDI – Kapitel 6

Autoren: Anna Wenzelburger, Moritz Fischer, Patrick Meyn

Assembler-Sprachen

In diesem Kapitel springen wir erst mal auf eine höhere Abstraktionsebene.

  • Architektur: Dies ist die Programmiersicht auf den Computer. Diese wird definiert durch Instruktionen (Operationen) und Operanden.
  • Mikroarchitektur: Hardware-Implementierung der Architektur. Das kommt im Detail in Kapitel 7.

Mit Assemblersprachen programmiert man in der Sprache des Computers.

Instruktionen / Befehle: Einzelne Worte
Befehlssatz: Gesamtes Vokabular

Befehle geben Art der Operation und ihre Operanden an. Dabei gibt es zwei Darstellungen:

  • Assemblersprache: für Menschen lesbare Schreibweise für Instruktionen
  • Maschinensprache: maschinenlesbares Format (1'en und 0'en)

MIPS Architektur:

Die MIPS Architektur wurde von John Hennessy und Kollegen in Stanford in den 1980ern entwickelt. Diese wird in vielen Computern und eingebetteten Systemen verwendet (Silicon Graphics, Nintendo, Sony, Cisco, Cavium, NetLogic, ...)

Sie eigntet sich gut zur Darstellung von allgemeinen Konzepten. Vieles davon ist auch auf andere Architekturen übertragbar.

Entwurfsprinzipien für Architekturen

John Hennessy (Stanford) und David Patterson (Berkeley):

1. Regularität vereinfacht den Entwurf

2. Mach den häufigsten Fall schnell

3. Kleiner ist schneller

4. ein guter Entwurf verlangt gute Kompromisse

Befehle: Addition

Hochsprache:

 a = b + c;

MIPS Assemblersprache:

 add a, b, c
  • add: Befehlsname (mnemonic) gibt die Art der auszuführenden Operationen an
  • b, c: Quelloperanden auf denen die Operation ausgeführt wird
  • a: Zieloperand in den das Ergebnis eingetragen wird.

Befehle: Subtraktion

Die Subtraktion ist ähnlich zur Addition. Nur der Befehlsname ändert sich

Hochsprache:

 a = b - c;

MIPS Assemblersprache:

 sub a, b, c
  • sub: Befehlsname (mnemonic) gibt die Art der auszuführenden Operationen an
  • b, c: Quelloperanden auf denen die Operation ausgeführt wird
  • a: Zieloperand in den das Ergebnis eingetragen wird.

Entwurfsprinzip 1

Regularität vereinfacht den Entwurf

  • Konsistentes Befehlsformat
  • Gleiche Anzahl von Operanden
Immer zwei Quellen, ein Ziel
Leichter zu kodieren und in Hardware zu bearbeiten

Befehle: Komplexere Abläufe

Die komplexeren Abläufe werden durch Folgen von einfachen Befehlen realisiert:

Hochsprache

 a = b + c + d;

 // Kommentare bis Zeilenende
 /*mehrzeiliger Kommentar*/

MIPS Assemblersprache:

 add t, b, c # t = b + c
 sub a, t, d # a = t - d

Entwurfsprinzip 2

Mach den häufigsten Fall schnell

MIPS enthält nur einfache, häufig verwendete Befehle. Die Hardware zur Dekodierung und Ausführung der Befehle kann einfach, klein und schnell sein. Komplexe Anweisungen (die nur seltener auftreten) können durch Folgen von einfachen Befehlen realisiert werden. MIPS ist ein Computer mit reduziertem Befehlssatz (reduced instruction set computer, RISC). Die Alternative dazu ist ein Computer mit komplexem Befehlssatz (complex instruction set computer, CISC).

Beispiel: Intel IA-32 / x86 (weit verbreitet in PCs)
Befehl: Kopiere Zeichenfolge im Speicher umher

Operanden

Ein Prozessor hat physikalische Speicherorte für dei Operanden von Befehlen. Diese möglichen Speicherorte sind:

  • Register
  • Speicher
  • konstante Werte (immediates)
stehen häufig direkt im Befehl

Operanden Register

Da der Speicher langsam ist, haben viele Architekturen eine kleine Anzahl von schnellen Registern. MIPS verfügt über 32 Register. Dabei ist jedes 32b breit. Deshalb wird MIPS auch "32b Architektur" genannt. Es gibt auch eine 64b-Version von MIPS, aber die behandeln wir hier nicht.

Entwurfsprinzip 3

Kleiner ist schneller

MIPS stellt nur eine kleine Anzahl von Registern bereit. Dies kann in schnellerer Hardware realisiert werden als ein größeres Registerfeld.

MIPS Registerfeld

Operanden: Register

  • Register
Register werden durch ein dem Namen vorangestelltes Dollar-Zeichen kenntlich gemacht
Beispiel: Das Register 0 wird geschrieben als "$0"
Gelesen als: "Register Null" oder "Dollar Null"
  • Bestimmte Register für bestimmte Verwendungszwecke:
Beispiele:
$0 enthält immer den konstanten Wert 0
Gesicherte Register $s0 - $s7 für das Speichern von Variablen
Temporäre Register $t0 - $t9 für das Speichern von Zwischenergebnissen, während einer komplizierten Rechnung
  • Zunächst benutzen wir nur
Temporäre Register $t0 - $t9
Gesicherte Register $s0 - $s7

Später mehr...

Rückblick add-Befehl

Hochsprache:

 a = b + c;

MIPS Assemblersprache:

 # $s0 = a, $s1 = b, $s2 = c
 add $so, $s1, $s2

Operanden: Speicher

  • Die Daten passen nicht alle in 32 Register
  • Lege die Daten im Hauptspeicher ab
  • Der Hauptspeicher ist groß (GB ... TB) und kann viele Daten halten
  • Der Hauptspeicher ist aber auch langsam
  • Speichere häufig verwendete Daten in Registern (auf sie kann schneller zugegriffen werden)
  • Kombiniere Register und Speicher zum Halten von Daten
Ziel: Greife schnell auf große Mengen von Daten zu

Wort-Adressierung von Daten im Speicher

Jedes 32-bit Datenwort hat eine eindeutige Adresse

Lesen aus wort-adressiertem Speicher

Lesen geschieht durch Ladebefehle (load). Der Befehlsname: load word (lw)

Beispiel: Lese ein Datenwort von der Speicheradresse 1 nach $s3

Adressarithmetik: Adressen werden relativ zu einem Register angegeben
Basisadresse $0 plus Distanz (offset) (1)
Adresse = $0 + 1 = 1
Jedes Register darf als Basisadresse verwendet werden
Nach Abarbeiten des Befehls hat $s3 den Wert 0xF2F1AC07

Assemblersprache (nicht MIPS!)

 lw $s3, 1($0)   # lese Wort 1 aus Speicher in $s3

Schreiben in wort-adressiertem Speicher

Schreiben geschieht durch Speicherbefehle (store). Der Befehlsname: store word (sw)

Beispiel: Schreibe (speichere) den Wert aus $t4 in Speicherwort 7. Der Offset kann dezimal (Standard) oder hexadezimal angegeben werden.

Adressarithmetik:

Basisadresse ($0) plus Offset (0x7)
Adresse: ($0 + 0x7) = 7
Jedes Register darf als Basisadresse verwendet werden

Assemblersprache (nicht MIPS!)

 sw $t4, 0x7 ($0)   # schreibe Wert aus $t4 in das Speicherwort 7

Byte-adressierbare Speicher

  • Jedes Byte hat eine individuelle Adresse
  • Speicherbefehle können auf Worten oder Bytes arbeiten
Worte: lw / sw
Bytes: lb / sb
  • Jedes Wort enthält vier Bytes
Adressen von Worten sind also Vielfache von 4

Lesen aus byte-adressiertem Speicher

Die Adresse eines Speicherwortes muss nun mit 4 multipliziert werden

Byte-Adresse von Wort 2 ist 2 * 4 = 8
Byte-Adresse von Wort 10 ist 10 * 4 = 40 (0x28)

Beispiel: Lade Wort 1 aus Byte-Adresse 4 nach $s3

Nach dem Befehl enthält $s3 den Wert 0xF2F1Ac07
  • MIPS ist byte-adressiert, nicht wort-adressiert!

MIPS Assemblersprache

 lw $s3, 4($0)   # Lese Wort 1 an Byte-Adresse 4 nach $s3

Schreiben in byte-adressiertem Speicher

  • Beispiel: Schreibe Wert aus $t7 in Speicheradresse 0x2C (44)

MIPS Assemblersprache

 sw $t7, 44($0)   # schreibe $t7 nach Byte-Adresse 44

Speicherorganisation: Big-Endian und Little-Endian

Schemata für Nummerierung von Bytes in einem Wort:

Wort-Adresse ist bei beiden gleich
  • Little-endian: Bytes werden vom niederwertigsten Ende an gezählt
  • Big-endian: Bytes werden vom höchstwertigen Ende an gezählt
  • Aus Jonathan Swift's Gullivers Reisen
Little-Endians schlagen Eier an der schmalen Seite auf
Big-Endians schlagen Eier an der breiten Seite auf

Welche Speicherorganisation benutzt wird, ist im Prinzip egal, außer wenn unterschiedliche Systeme Daten austauschen müssen.

Beispiel: Big-Endian und Little-Endian

Wir nehmen an, dass $t0 den Wert 0x23456789 enthält. Im Programm:

 sw $t0, 0($0)
 lb $s0, 1($0)

Frage, an welchem Wert hat $s0 nach der ausfürhung auf einem...

  • ... Big-Endian Prozessor? 0x00000045
  • ... Little-Endian Prozessor? 0x00000067

Entwurfsprinzip 4

Ein guter Entwurf verlangt gute Kompromisse

Mehrere Befehlsformate erlauben Flexibilität...

add, sub: verwenden drei Register als Operanden
lw, sw: verwenden zwei Register und eine Konstante als Operanden

... aber Anzahl von Befehlsformaten sollte klein sein

Entwurfsprinzip 1: Regularität vereinfacht Entwurf
Entwurfsprinzip 3: Kleiner ist schneller

Operanden: Konstante Werte in Befehl (immediates)

lw und sw zeigen die Verwendung von konstanten Werten (immediates)

Direkt im Befehl untergebracht, deshalb auch Direktwerte genannt
Brauchen kein eigenes Register oder Speicherzugriff

Der Befehl "add immediate" (addi) addiert Direktwert auf Register. Der Direktwert ist eine 16b Zweierkomplementzahl

Hochsprache

 a = a + 4;
 b = a - 12;

MIPS Assemblersprache

 # $s0 = a; $s1 = b
 addi $s0, $s0, 4
 addi $s1, $s0, -12

Maschinensprache

Der Computer versteht nur 0'en und 1'en. Die Maschinensprache ist die Binärdarstellung von Befehlen.

32b Befehle -> Regularität vereinfacht den Entwurf: Daten und Befehle sind beides 32b Worte.

Es gibt drei Befehlsformate:

  • R-Typ: Operanden sind nur Register
  • I-Typ: Register und ein Direktwert
  • J-Typ: für Programmsprünge

Befehlsformat R-Typ

R-Typ steht für Register Typ. Ein R-Typ hat 3 Registeroperanden:

rs, rt: Quellregister
rd: Zielregister

Dabei gibt es auch andere Angaben, die binär kodierte Befehle sind:

op: Operations-Code oder Opcode (ist 0 für Befehle vom R-Typ)
funct: Auswahl der genauen Funktion. Dabei ergeben der Opcode und die Funktion zusammen die auszuführende Operation
shamt: Schiebeweite für Shift-Befehle, sonst 0

Beispiele für R-Typ Befehle:

Assemblersprache

 add $s0, $s1, $s2
 sub $t0, $t3, $t5

Beachte die Reihenfolge der Register ist eine andere als in der Assembler-Sprache.

Befehlsformat I-Typ

I-Typ steht für Immediate Typ. Ein I-Typ hat 3 Registeroperanden:

rs: Quellregister
rt: Zielregister
imm: 16b Direktwert im Zweierkomplement

Dabei gibt es auch andere Angaben, die binär kodierte Befehle sind:

op: Opcode
Regularität vereinfacht hier den Entwurf: Alle Befehle haben einen Opcode. Dabei wir die Operation beim I-Typ nur durch den Opcode bestimmt.

Es ist keine Angabe über die Funktion nötig (oder vorhanden)!

Beispiele für I-Typ Befehle

Assemblersprache

 addi $s0, $s1, 5
 addi $t0, $s3, -12
 lw $t2, 32 ($0)
 sw $s1, 4($t1)

Beachte die unterschiedliche Reihenfolge von Registern in Assembler- und Maschinensprache!

 addi rt, rs, imm
 lw rt, imm(rs)
 sw rt, imm(rs)

Befehlsformat J-Typ

J-Typ steht für Jump-Typ und hat einen 26b Adressoperand (addr). Der J-Typ wird für Spurngbefehle (j) verwendet.

Übersicht der Befehlsformate

Flexibilität durch gespeicherte Programme

32b Befehle und Daten leigen im Speicher. Folgen von Befehlen bestimmen das Verhalten.

Einziger Unterschied zwischen zwei Anwendungen

Das Ausführen von unterschiedlichen Programmen geschieht ohne Neuverdrahten oder Neubau von Hardware. Das neue Programm wird nur als Maschinensprache im Speicher abgelegt.

Die Hardware des Prozessors führt Programme schrittwiese aus:

Sie holt neue Befehle aus dem Speicher (fetch) in der richtigen Reihenfolge
Sie führt die im Befehl verlangte Operation aus

Der Programmzähler (program counter, PC) zeigt die Adresse des auszuführenden Befehls an.

Bei MIPS: Programmausführung beginnt auf der Adresse 0x00400000

Im Speicher abgelegtes Programm

Maschinensprache verstehen

  • Man beginnt mit dem Entschlüsseln des Opcodes
  • Der Opcode bestimmt die Bedeutung der anderen Bits
  • Wenn der Opcode Null ist
liegt ein Befehl im R-Format vor
Die Operation wird durch das Funktionsfeld bestimmt
  • Sonst
bestimmt der Opcode alleine die Operation, mehr dazu im Anhang B im Buch

Programmierung

  • Hochsprachen werden auf einer abstrakten Ebene programmiert
z.B. C, Java, Python, Scheme
  • Häufige Konstrukte in Hochsprachen sind:
if/else-Anweisungen
for-Schleifen
while-Schleifen
Feld (Array) Zugriffe
Prozeduraufrufe
  • Andere nützliche Anweisungen sind:
Arithmetische/logische Ausdrücke
Verzweigungen

Logische Befehle

 and, or, xor, nor
  • and: nützlich zum Maskieren von Bits
Ausmaskieren aller Bits außer dem LSB:
0xF234012F AND 0x000000FF = 0x0000002F
  • or: nützlich zum Vereinigen von Bitfeldern
Vereinige 0xF2340000 mit 0x000012BC:
0xF2340000 OR 0x000012BC = 0xF23412BC
  • nor: nützlich zur Invertierung von Bits:
A NOR $0 = NOT A
  • andi, ori, xori
Der 16-bit Direktwerte wird erweitert mit führenden Nullbits (nicht vorzeichenerweitert)
  • nori wird nicht benötigt

Beispiel: Logische Befehle

Schiebebefehle

  • sll: shift left logical
Beispiel: sll $t0, $t1, 5
# $t0 <= $t1 << 5
  • srl: shift right logical
Beispiel: srl $t0, $t1, 5
# $t0 <= $t1 >> 5
  • sra: shift right arithmetic
Beispiel: sra $t0, $t1, 5
# $t0 <= $t1 >>> 5

Schieben mit variabler Distanz:

  • sllv: shift left logical variable
Beispiel: sllv $t0, $t1, $t2
# $t0 <= $t1 << $t2
  • srlv: shift right logical variable
Beispiel: srlv $t0, $t1, $t2
# $t0 <= $t1 >> $t2
  • srav: shift right arithmetic variable
Beispiel: srav $t0, $t1, $t2
# $t0 <= $t1 >>> $t2

Handhabung von Konstanten

  • 16-Bit Konstante mit addi:

Hochsprache:

 //int ist ein vorzeichenbehaftetes 32b Wort
 int a = 0x4f3c;

MIPS Assemblersprache:

 # $s0 = a
 addi $s0, $0, 0x4f3c
  • 32-Bit Konstante mit Load Upper Immediate (lui) und ori:

(lui lädt den 16-Bit Direktwert in die obere Registerhälfte und setzt die untere Hälfte auf 0)

Hochsprache:

 int a = 0xFEDC8765;

MIPS Assemblersprache:

 # $s0 = a
 lui $s0, 0xFEDC
 ori $s0, $s0, 0x8765

Multiplikation und Division

Hierfür verwenden wir die Spezialregister lo, hi

  • Eine 32b x 32b Multiplikation ergibt ein 64b Produkt
    mult $s0, $s1
    Das Ergebnis steht in {hi, lo}
  • Eine 32b Division ergibt 32b Quotient und einen 32b Rest
    div $s0, $s1

Der Quotient steht in lo Der Rest steht in hi

  • Lesen von Daten aus den Spzeialregistern ("move from...")
mflo $s2
mfhi $s3

Verzweigungen und Sprünge

Verzweigungen und Sprünge ändern die Ausführungsreihenfolge von Befehlen. Dabei gibt es zwei verschiedene Arten von Verzweigungen:

  • Bedingte Verzweigungen:
branch if equal (beq): Verzweige, wenn gleich
branch if not equal (bne): Verzweige, wenn ungleich
  • Unbedingte Verzweigung:
jump (j): Springe
jump register (jr): Springe auf Adresse aus Register
jump and link (jal): Springe und merke die Adresse des nächsten Befehls

Wiederholung: Programm im Speicher

Bedingte Verzweigungen (beq)

MIPS Assemblersprache

    addi $s0, $0, 4       # $s0 = 0 + 4 = 4
    addi $s1, $0, 1       # $s1 = 0 + 1 = 1
    sll  $s1, $s1, 2      # $s1 = 1 << 2 = 4
    beq  $s0, $s1, target # Verzweigung wird genommen
    addi $s1, $s1, 1      # nicht ausgeführt
    sub  $s1, $s1, $s0    # nicht ausgeführt

 target:                  # Positionsmarkierung (label)
    add  $s1, $s1, $s0    # $s1 = 4 + 4 = 8

Label sind Namen für Stellen (Adressen) im Programm. Sie müssen anders als Mnemonics heißen und haben einen Doppelpunkt am Ende. (Sie dürfen also nicht add, addi, beq, sub, sll usw. lauten)

Nicht genommene Sprünge (bne)

MIPS Assemblersprache

    addi $s0, $0, 4       # $s0 = 0 + 4 = 4
    addi $s1, $0, 1       # $s1 = 0 + 1 = 1
    sll  $s1, $s1, 2      # $s1 = 1 << 2 = 4
    bne  $s0, $s1, target # Verzweigung nicht genommen
    addi $s1, $s1, 1      # $s1 = 4 + 1 = 5
    sub  $s1, $s1, $s0    # $s1 = 5 – 4 = 1

 target:
    add $s1, $s1, $s0     # $s1 = 1 + 4 = 5

Unbedingte Verzweigungen/Sprünge (j)

MIPS Assemblersprache

    addi $s0, $0, 4      # $s0 = 4
    addi $s1, $0, 1      # $s1 = 1
    j    target          # Springe zu target
    sra  $s1,  $s1, 2    # nicht ausgeführt
    addi $s1,  $s1, 1    # nicht ausgeführt
    sub  $s1,  $s1,  $s0 # nicht ausgeführt

 target:
    add  $s1, $s1, $s0   # $s1 = 1 + 4 = 5

Unbedingte Verzweigungen (jr)

MIPS Assemblersprache

 0x00002000    addi $s0, $0, 0x2010
 0x00002004    jr   $s0
 0x00002008    addi $s1, $0, 1
 0x0000200C    sra  $s1, $s1, 2
 0x00002010    lw   $s3, 44($s1)

Konstrukte in Hochsprachen

In Hochsprachen gibt es folgende Konstrukte:

  • if-Anweisung
  • if/else-Anweisungen
  • while-Schleifen
  • for-Schleifen

If-Anweisung

Hochsprache:

 if (i == j)
    f = g + h;
 f = f - i;

MIPS Assemblersprache

 # $s0 = f, $s1 = g, $s2 = h
 # $s3 = i, $s4 = j
       bne $s3, $s4, L1
       add $s0, $s1, $s2

 L1:   sub $s0, $s0, $s3

If/Else-Anweisung

Hochsprache:

 if (i == j)
    f = g + h;
 else
    f = f - i;

MIPS Assemblersprache

 # $s0 = f, $s1 = g, $s2 = h
 # $s3 = i, $s4 = j
           bne $s3, $s4, L1
           add $s0, $s1, $s2
           j   done
 L1:       sub $s0, $s0, $s3
 done:

While-Schleife

Hochsprache:

 // berechne x = ld 128

 int pow = 1;
 int x = 0;

 while (pow != 128) {
 pow = pow * 2;
 x = x + 1;
 }

MIPS Assemblersprache

 # $s0 = pow, $s1 = x
             addi $s0, $0, 1
             add  $s1, $0, $0
             addi $t0, $0, 128 
 while:      beq  $s0, $t0, done
             sll  $s0, $s0, 1
             addi $s1, $s1, 1
             j    while
 done:

For-Schleife

Eine for-Schleife ist wie folgt aufgebaut:

 for (Initialisierung; Bedingung; Schleifenanweisung)
       Schleifenrumpf
  • Initialisierung: wird einmal vor der Ausführung der Schleife ausgeführt
  • Bedingung: wird vor Beginn jedes Schleifendurchlaufs geprüft
  • Schleifenanweisung: wird am Ende jedes Schleifendurchlaufs ausgeführt
  • Schleifenrumpf: wird einmal ausgeführt, wenn die Bedingung wahr ist

For-Schleife

Hochsprache

 // addiere Zahlen von 0 bis 9 auf
 int sum = 0;
 int i;

 for (i = 0; i != 10; i = i + 1) {
     sum = sum + i;
 }

MIPS Assemblersprache

 # $s0 = i, $s1 = sum
          addi $s1, $0, 0
          add  $s0, $0, $0
          addi $t0, $0, 10
 for:     beq  $s0, $t0, done
          add  $s1, $s1, $s0
          addi $s0, $s0, 1
          j    for
 done:

Kleiner-als Vergleiche

Hochsprache

 // addiere Zweierpotenzen
 // kleiner gleich 100
 int sum = 0;
 int i;

 for (i = 1; i < 101; i = i * 2) {
     sum = sum + i;
 }

MIPS Assemblersprache

 # $s0 = i, $s1 = sum
          addi $s1, $0, 0
          addi $s0, $0, 1
          addi $t0, $0, 101
 loop:    slt  $t1, $s0, $t0
          beq  $t1, $0, done
          add  $s1, $s1, $s0
          sll  $s0, $s0, 1
          j    loop
 done:

$t1 = 1 if i < 101

Datenfelder (arrays)

Arrays sind nützlich, um auf eine große Zahl von Daten gleichen Typs zuzugreifen. Zugegriffen wir auf ein einzelnes Element über einen Index.

Die Größe eines Arrays ist die Anzahl von Elementen im Array.

Verwendung von Arrays

In unserem Beispiel betrachten wir ein Array mit 5 Elementen.

Die Basisadresse hier ist 0x12348000. Dies ist die Adresse des ersten Array-Elements. Diese Adresse hat den Index 0, geschrieben als array[0].

Der erste Schritt für den Zugriff auf Elemente: Lade die Basisadresse des Arrays in die Register.

Hochsprache:

 int array[5];
 array[0] = array[0] * 2;
 array[1] = array[1] * 2;

MIPS Assemblersprache

 # Basisadresse von array = $s0

 lui $s0, 0x1234      # lade 0x1234 in die obere Hälfte von $s0
 ori $s0, $s0, 0x8000 # lade 0x8000 in die untere Hälfte von $s0

 lw  $t1, 0($s0)      # $t1 = array[0]
 sll $t1, $t1, 1      # $t1 = $t1 * 2
 sw  $t1, 0($s0)      # array[0] = $t1

 lw  $t1, 4($s0)      # $t1 = array[1]
 sll $t1, $t1, 1      # $t1 = $t1 * 2
 sw  $t1, 4($s0)      # array[1] = $t1

Bearbeite Array in for-Schleife

Hochsprache

 int array[1000];
 int i;
 for (i=0; i < 1000; i = i + 1)
       array[i] = array[i] * 8;

MIPS Asseblersprache

 # $s0 = Basisadresse von array, $s1 = i

 #Initialisierung

    lui  $s0, 0x23B8      # $s0 = 0x23B80000
    ori  $s0, $s0, 0xF000 # $s0 = 0x23B8F000
    addi $s1, $0, 0       # i = 0
    addi $t2, $0, 1000    # $t2 = 1000

 loop:
    lst  $t0, $s1, $t2    # i < 1000?
    beq  $t0, $0, done    # if not then done
    sll  $t0, $s1, 2      # $t0 = i * 4 (byte offset)
    add  $t0, $t0, $s0    # address of array[i]
    lw   $t0, 0($t0)      # $t1 = array[i]
    sll  $t0, $t1, 3      # $t1 = array[i] * 8
    sw   $t0, 0($t0)      # array[i] = array[i] * 8
    addi $t0, $s1, 1      # i = i + 1
    j    loop             # repeat
 done:

Zeichendarstellung im ASCII-Code

ASCII steht für American Standard Code for Information Interchange.

ASCII definiert für gängige Textzeichen einen 7b breiten Code. Dies ist einfach, aber schon älter. Heute wird der Unicode verwendet. Er hat eine breitere Darstellung für alle Textzeichen.

Beispiel: "S" = 0x53, "a" = 0x61, "A" = 0x41

Klein- und Großbuchstaben liegen um 0x20 (32) Bit auseinander.

Prozedur- und Funktionsaufruf

Definitionen:

  • Aufrufer: Ursprung des Prozeduraufrufs (hier main)
  • Aufgerufener: aufgerufene Prozedur (hier sum)

Hochsprache

 void main()
 {
    int y; 
    y = sum (42, 7);
 ...
 }
 int sum (int a, int b)
 {
    return (a + b);
 }

Aufrufkonventionen:

  • Aufrufer:
Übergibt Argumente (aktuelle Parameter) an Aufgerufenen
Springt Aufgerufenen an
  • Aufgerufener:
Führt Prozedur/Funktion aus
Gibt Ergebnis (Rückgabewert) an Aufrufer zurück (für Funktion)
Springt hinter Aufrufsteller zurück
Darf keine Register oder Speicherstellen überschreiben, die im Aufrufer genutzt werden

Konventionen für MIPS:

Prozeduraufruf: “jump and link (jal)“
Rücksprung: “jump register (jr)”
Register für Argumente: $a0 - $a3
Register für Ergebnis: $v0

Hochsprache

 int main() {
    simple ();
    a = b + c;
 }

 void simple () {
    return;
 }

MIPS Assemblersprache

 0x00400200 main:   jal simple
 0x00400204         add $s0, $s1, $s2
 ...
 0x00401020 simple: jr $ra

Aufrufargumente und Rückgabewert

MIPS Konventionen:

  • Argumentwerte (aktuelle Parameter): $a0 - $a3
  • Rückgabewert (Funktionswert, Ergebnis): %v0

Hochsprache

 int main()
 {
    int y;
    ...
    y = diffofsums (2, 3, 4, 5); // 4 Argumente, aktuelle Parameter
    ...
 }
 int diffofsums (int f, int g, int h, int i) // 4 formale Parameter
 {
    int result;
    result = (f + g) - (h + i);
    return result; // Rückgabewert
 }

MIPS Assemblersprache

 # $s0 = y
 main:
    ...
    addi $a0, $0, 2    # Argument 0 = 2
    addi $a1, $0, 3    # Argument 1 = 3
    addi $a2, $0, 4    # Argument 2 = 4
    addi $a3, $0, 5    # Argument 3 = 5
    jal  diffofsums    # Prozeduraufruf
    add  $s0, $v0, $0  # y = Rückgabewert
    ...

 # $s0 = Rückgabewert
 diffofsums:
    add  $t0, $a0, $a1 # $t0 = f + g
    add  $t1, $a2, $a3 # $t1 = h + i
    sub  $s0, $t0, $t1 # result = (f + g) - (h + i)
    add  $v0, $s0, $0  # Lege Rückgabewert in $v0 ab
    jr   $ra           # Rücksprung zum Aufrufer
  • diffofsums überschreibt drei Register $t0, $t1 und $s0
  • diffofsums kann benötigte Register temporär auf dem Stack sichern.

Stack (auch Stapel- oder Kellerspeicher)

Ein Stack ist ein Speicher für temporäres Zwischenspeichern von Werten. Dabei agiert er wie ein Stapel (Beispiel: Teller).

  • Zuletzt aufgelegter Teller wird zuerst herunter genommen
  • "last in, first out" (LIFO)
  • Dabei dehnt sich der Stack aus: Er belegt mehr Speicher, wenn mehr Daten unterzubringen sind
  • Er kann sich auch zusammenziehen: Er belegt weniger Speicher, wenn zwischengespeicherte Daten nicht mehr gebraucht werden.

Stack

Der Stack wächst bei MIPS nach unten (von hohen zu niedrigeren Speicheradressen)

Dies ist die übliche Realisierung (deshalb auch Kellerspeicher genannt)

Ein Stack hat einen Stapelzeiger ("stack pointer"): $sp

Dieser zeigt auf das zuletzt auf den Stack abgelegte Datenelement

Verwendung des Stacks in Prozeduren

Aufgerufenen Prozeduren dürfen keine unbeabsichtigten Nebenwirkungen ("Seiteneffekte") haben.

Problem: diffofsums überschreibt die drei Register $t0, $t1, $s0

MIPS Assemblerpsrache

 # $s0 = result
 diffofsums:
    add $t0, $a0, $a1 # $t0 = f + g
    add $t1, $a2, $a3 # $t1 = h + i
    sub $s0, $t0, $t1 # result = (f + g) - (h + i)
    add $v0, $s0, $0  # Lege Rückgabewert in $v0 ab
    jr  $ra           # Rücksprung zum Aufrufer

Register auf Stack zwischenspeichern

 # $s0 = result
 diffofsums:
    addi $sp, $sp, -12 # 3*4 Bytes auf Stack anfordern um drei 32b Register zu sichern
    sw   $s0, 8($sp)   # speichere $s0 auf Stack
    sw   $t0, 4($sp)   # speichere $t0 auf Stack
    sw   $t1, 0($sp)   # speichere $t1 auf Stack

    add  $t0, $a0, $a1 # $t0 = f + g
    add  $t1, $a2, $a3 # $t1 = h + ii)
    sub  $s0, $t0, $t1 # result = (f + g) - (h + 
    add  $v0, $s0, $0  # Lege Rückgabewert in $v0 ab

    lw   $t1, 0($sp)   # stelle $t1 wieder vom Stack her
    lw   $t0, 4($sp)   # stelle $t0 wieder vom Stack her
    lw   $s0, 8($sp)   # stelle $s0 wieder vom Stack her
    addi $sp, $sp, 12  # Platz auf Stack wird nicht mehr benötigt, wieder freigeben

    jr   $ra           # Rücksprung zum Aufrufer

Veränderung des Stacks während diffofsums

Sicherungskonventionen für Register

Mehrfache Prozeduraufrufe: Sichern von $ra

 proc1:
    addi $sp, $sp, -4 # Platz auf Stack anlegen
    sw   $ra, 0($sp)  # sichere $ra auf Stack
    jal  proc 2
    ...
    lw   $ra, 0($sp)  # stelle $ra vom Stack wieder her
    addi $sp,$sp, 4   # Stapelspeicher wieder freigeben
    jr   $ra          # Rückkehr zum Aufrufer von proc1

Erhalten von Registern mittels Stack

 # $s0 = result
 diffofsums:
    addi $sp, $sp, -4  # Platz auf Stack für 4 Bytes anlegen, reicht zum Sichern eines Registers
    sw   $s0, 0($sp)   # sichere $s0 auf Stack, $t0 und $t1 brauchen nicht erhalten zu werden!
    add  $t0, $a0, $a1 # $t0 = f + g
    add  $t1, $a2, $a3 # $t1 = h + i
    sub  $s0, $t0, $t1 # result = (f + g) - (h + i)
    add  $v0, $s0, $0  # Lege Rückgabewert in $v0 ab
    lw   $s0, 0($sp)   # stelle $s0 vom Stack wieder her
    addi $sp, $sp, 4   # Gebe nicht mehr benötigten Speicher auf Stack frei
    jr   $ra           # Rücksprung zum Aufrufer

Rekursive Prozeduraufrufe

Hochsprache

 int fakultaet (int n) {
    if (n <= 1)
        return 1;
    else
        return (n * fakultaet (n-1));
 }

MIPS Assemblersprache

 0x90 fakultaet: addi  $sp, $sp, -8      # Platz für zwei Register 
 0x94            sw    $a0, 4($sp)       # sichere $a0 
 0x98            sw    $ra, 0($sp)       # sichere $ra
 0x9C            addi  $t0, $0, 2 
 0xA0            slt   $t0, $a0, $t0     # n <= 1 ?
 0xA4            beq   $t0, $0, else     # nein: weiter bei else
 0xA8            addi  $v0, $0, 1        # ja: gebe 1 zurück
 0xAC            addi  $sp, $sp, 8       # Platz wieder freigeben
 0xB0            jr    $ra               # Rücksprung
 0xB4            else: addi $a0, $a0, -1 # n = n - 1
 0xB8            jal   fakultaet         # rekursiver Aufruf
 0xBC            lw    $ra, 0($sp)       # wiederherstellen von $ra
 0xC0            lw    $a0, 4($sp)       # wiederherstellen von $a0
 0xC4            addi  $sp, $sp, 8       # Platz wieder freigeben
 0xC8            mul   $v0, $a0, $v0     # n * fakultaet(n - 1)
 0xCC            jr    $ra               # Rücksprung

Veränderung des Stacks bei rekursivem Aufruf

Zusammenfassung: Prozeduraufruf

  • Aufrufer
Lege die Aufrufparameter (aktuelle Parameter) in $a0-$a3 ab
Sichere die zusätzlich benötigte Register auf dem Stack ($ra, manchmal auch $t0-t9) - Entsprechende Konvention über Erhaltung von Registern
jal aufgerufener
Stelle die gesicherte Register wieder her
Hole evtl. Rückgabewert aus $v0 (bei Funktionen)
  • Aufgerufener
Sichere die zu erhaltende verwendete Register auf dem Stack (üblicherweise $s0-$s7)
Führe die gewünschten Berechnungen der Prozedur aus
Lege den Rückgabewert in ab $v0 (bei Funktionen)
Stelle die gesicherten Register wieder her
jr $ra

Adressierungsarten

Wo kommen Operanden für Befehle her?

Aus einem Register
Direktwert aus Instruktionen
Relativ zu einer Basisadresse - Sonderfall: Relativ zum Programmzähler
Pseudodirekt

Aus Register (register operands)

  • Beispiel: add $s0, $t2, $t3
  • Beispiel: sub $t8, $s1, $0

Direktwert aus Instruktion (immediate)

16b Direktwert als Operand verwenden

  • Beispiel: addi $s4, $t5, -73
  • Beispiel: ori $t3, $t7, 0xFF

Relativ zu einer Basisadresse

Adresse eines Operanden im Speicher ist: Basisadresse + Vorzeichenerweiterter Direktwert

  • Beispiel: lw $s4, 72($0)
Adresse = $0 + 72
  • Beispiel: sw $t2, -25($t1)
Adresse = $t1 - 25

Relativ zur nächsten Adresse im Programmzähler

 0x10       beq  $t0, $0, else
 0x14       addi $v0, $0, 1
 0x18       addi $sp, $sp, i
 0x1C       jr   $ra
 0x20 else: addi $a0, $a0, -1
 0x24       jal  fakultaet

Pseudodirekte Operanden

Auffüllen von entfallenen Bits (mit Nullen und PC+4[31:28])

 0x0040005C      jal sum
 ...       
 0x004000A0 sum: add $v0, $a0, $a1

Compilieren und Ausführen einer Anwendung

Was muss im Speicher abgelegt werden?

Im Speicher muss abgelegt werden:

  • Instruktionen (historisch auch genannt Text)
  • Daten
Globale und statische Daten werden angelegt vor Beginn der Programmausführung
Dynamische Daten werden während der Programmausführung angelegt
  • Speicherobergrenze bei MIPS (-32)?:
Maximal 232 = 4 Gigabytes (4 GB)
Von Adresse 0x00000000 bis 0xFFFFFFFF

MIPS Speicherorganisation (memory map)

Beispielprogramm in "C"

 int f, g, y; // globale Variablen

 int main(void)
 {
    f = 2;
    g = 3;
    y = sum(f, g);

    return y;
 }

 int sum(int a, int b) {
    return (a + b);
 }

Beispielprogramm: MIPS Assemblersprache

 .data
 f: .space 4           # Direktiven für Assembler
 g: .space 4           # jeweils ein Wort, initialisiert
 y: .space 4           # auf den Wert 0
 .text
 main:
    addi $sp, $sp, -4  # Stack Frame anlegen
    sw   $ra, 0($sp)   # sichere $ra
    addi $a0, $0, 2    # $a0 = 2
    sw   $a0, f        # f = 2
    addi $a1, $0, 3    # $a1 = 3
    sw   $a1, g        # g = 3
    jal  sum           # Aufruf von sum
    sw   $v0, y        # y = sum()
    lw   $ra, 0($sp)   # stelle $ra wieder her
    addi $sp, $sp, 4   # stelle $sp wieder her
    jr   $ra           # Rückkehr ins Betriebssystem
 sum:
    add $v0, $a0, $a1  # $v0 = a + b
    jr   $ra           # return

Beispielprogramm: Symboltabelle

Beispielprogramm: Ausführbare Datei

Beispielprogramm im Speicher

Dies und Das

  • Pseudobefehle
  • Ausnahmebehandlung (exceptions)
  • Befehle für vorzeichenbehaftete und vorzeichenlose Zahlen
  • Gleitkommabefehle

Beispiele für Pseudobefehle

Ausnahmebehandlung (exceptions)

  • Abweichen von der normalen Ausführungsreihenfolge von Befehlen
Beim Auftreten außergewöhnlicher Umstände (exception)
Automatischer Aufruf spezieller Prozedur: Ausnahmebehandlung (exception handler)
  • Auslösung der Ausnahmebehandlung z.B durch:
Hardware - dies wird dann Interrupt genannt (z.B. Tippen einer Taste auf der Tastatur)
Software - dies wird dann Trap genannt (z.B. der Versuch der Ausführung einer unbekannten Instruktion)
  • Beim Auftreten der Ausnahme geschieht folgendes:
Der Grund der Ausnahme wird gespeichert.
Sprung zur Ausnahmebehandlung auf der Adresse 0x80000180
Dann Wiederaufnahme der normalen Programmausführung

Spezialregister für Ausnahmebehandlung

Diese Spezialregister für Ausnahmebehandlungen liegen außerhalb des regulären Registerfeldes.

  • Cause Enthält den Grund der Ausnahme
  • EPC( Exception PC) enthält den regulären PC an dem die Ausnahme auftrat
  • EPC und Cause: sind nicht Bestandteil des "eigentlichen" MIPS-Prozessors. Sie werden ausgelagert in den Coprozessor (unterstützt den Hauptprozessor). Genauer, EPC und Cause werden in den Coprozessor 0 ausgelagert.
  • Datenaustausch mit dem Coprozessor (hier nur lesen)
"Move from Coprozessor 0"
mfc 0 $t0, EPC

Dies lädt den Inhalt des Spezialregisters EPC in das reguläre Register $t0.

Analog funktioniert das auch für Cause.

Auslöser für Ausnahmen

Ausnahmen

Der Prozessor speichert bei einer Ausnahme den Grund und den Auftritts-PC in Cause und EPC.
Der Prozessor springt bei der Ausnahmebehandlung an die Adresse 0x80000180.

Die Ausnahmebehandlung funktioniert wie folgt:

Speichere die Register auf dem Stack
Lese Cause in das Spezialregister - mfc0 $t0, Cause
Bearbeite die Ausnahme
Stelle alle Register wieder her
Springe zurück ins eigentlich laufende Programm
mfc0 $k0, EPC
jr $k0

Vorzeichenbehaftete und -lose Befehle

Vorzeichenbehaftete und -lose Befehle sind zum Beispiel:

  • Addition und Subtraktion
  • Multiplikation und Division
  • Set-less-than

Addition und Subtraktion

  • Vorzeichenbehaftet: add, addi, sub
Gleiche Operation wie bei der vorzeichenlosen Versionen
Aber: Prozessor löst Ausnahme bei arithmetischem Überlauf aus
  • Vorzeichenlos: addu, addiu, subu
Prüft nicht auf Überlauf
Hinweis: addiu - erweitert den Direktwert mit seinem Vorzeichen

Multiplikation und Division

  • Vorzeichenbehaftet: mult, div
  • Vorzeichenlos: mutlu, divu

Set Less Than

  • Vorzeichenbehaftet: slt, slti
  • Vorzeichenlos: sltu, sltiu
Hinweis: sltiu - erweitert den Direktwert mit seinem Vorzeichen vor dem Vergleich mit dem Register

Das u an den Mnemonics steht für unsigned.

Laden von 8b und 16b breiten Daten

  • Vorzeichenbehaftet:
Vorzeichenerweitere schmale Daten auf volle 32b Registerbreite
Load halfword: lh
Load byte: lb
  • Vorzeichenlos:
Fülle schmale Daten mit Nullen auf volle 32b Registerbreite auf
Load halfword unsigned: lhu
Load byte: lbu

Gleitkommabefehle

  • Gleitkommabefehle sind nicht Bestandteil des "eigentlichen" MIPS-Prozessors. Diese werden in dem Gleitkommaprozessor (Coprocessor 1) bearbeitet.
  • 32 32-bit Gleitkommaregister ($f0 - %f31) - mit einer Genauigkeit von single precision
  • Werte mit doppelter Genauigkeit benötigen je zwei aufeinanderfolgende Register
z.B. $f0 und $f1, $f2 und $f3 usw
  • Double precision-Register sind also $f0, $f2, $f4 usw.

Gleitkommabefehle

Format für F-Typ Instruktionen

  • Opcode = 17 (010001)
  • Single-precision:
cop = 16 (010000)
add.s, sub.s, div.s, neg.s, abs.s etc.
  • Double-precision:
cop = 17 (010001)
add.d, sub.d, div.d, neg.d, abs.d etc.
  • Drei Registeroperanden:
fs, ft: Quelloperanden
fd: Zieloperanden

Weitere Gleitkommabefehle

  • Setzt boole’sches Spezialregister bei Vergleichen: fpcond
Gleicheit: c.seq.s, c.seq.d
Kleiner-als: c.lt.s, c.lt.d
Kleiner-als-oder-gleich: c.le.s, c.le.d
Beispiel: c.lt.s $f 1 $fs2
  • Bedingte Verzweigung abhängig von Spezialregister
bc1f: springt falls fpcond = FALSE
bc1t: springt falls fpcond = TRUE
Beispiel: bc1f toosmall
  • Loads und Stores: jeweils Single precision
lwc1: lwc1 $ft1, 42($s1)
swc1: swc1 $fs2, 17($sp)
Double precision braucht je zwei Anweisungen

  

zum Seitenanfang