x86 Assembler-Programmierung (1)
Transcrição
x86 Assembler-Programmierung (1)
MMX – Varianten der Arithmetik Arithmetik wahlweise im Wraparound-Modus: Unter- bzw. Überläufe werden abgeschnitten und nur die niederwertigen (dargestellten) Bits abgebildet Saturations-Modus: Kein Überlauf oder Unterlauf, stattdessen Abbildung auf kleinste bzw. größte Werte. Sinnvoll z.B. bei Berechung einer Darstellungsfarbe F000h a2 a1 a0 + + + + 3000h b2 b1 b0 2000h a2+b2 a1+b1 a0+b0 F000h a2 a1 a0 + + + + 3000h b2 b1 b0 FFFFh a2+b2 a1+b1 a0+b0 Peter Sobe 68 Anwendungsbeispiel Anwendungsbeispiel Sum of absolute Differences (SAD), eine zeitkritische Operation bei der Videokompression: SAD (dx, dy ) y0 N 1 x0 N 1 I n y0 m x0 K (m, n) I K 1 (m dx, n dy ) Manipulation n,m Lade jeweils 8 aufeinanderfolgende Werte Lade IK(m,n) bis IK(m+7,n) nach mm0 Lade IK-1(m+dx,n+dy) bis IK-1(m+dx+7,n+dy) nach mm1 MOVQ mm2, mm0 ; mm2 <-mm0 PSUBUSB mm0, mm1 ; mm0 <- mm0 - mm1 PSUBUSB mm1, mm2 ; mm1 <- mm1 – mm2 POR mm0, mm1 Absolute Differenzen für die 8 Werte MOVQ [esi], mm0 ADD esi, 8 j n,m-Bereich durchlaufen? n Summierung der absoluten Differenzen 69 Peter Sobe Leistungszuwachs Dokumentierter Leistungszuwachs durch MMX: Gewinn bei Übergang von skalarem C-Code zu MMX-Code RGB YUV Umwandlung: Inverse DCT 2D 8x8: Absolute Differences: Matrix-Vektor-Multiplikation: >10 3.5 5 14.6 Geschwindigkeitsgewinn größer 8 u.a. durch Multiply-Add-Befehl Code wird von Prozessorherstellern (Intel, AMD) als so genannte „Application Notes“ veröffentlicht Peter Sobe 70 MMX, SSE - Weiterentwicklung • SSE2: 2x64-Bit FloatingPoint, Zwischenergebnisse nur 64 Bit anstatt der 80 Bit bei 8087, schneller aber geringere Genauigkeit • SSE3: horizontale Operationen – arithmetische Operationen über Subwörter innerhalb eines Registers • SSE4: Integer 2x32-zu-64 Bit Multiplikation ohne Überlauf, mehrfache Multiplikation und Aufsummieren (Skalarprodukt), MPSADBW-Befehl: Summe acht absoluter 8-Bit-Differenzen (SSE4.1) hardwareseitige CRC-32-Prüfsummenbildung (SSE 4.2), Stringverarbeitung (SSE 4.2) Angekündigt: AVX (Advanced Vector Extensions) mit 256 Bit SIMD-Mode Peter Sobe 71 SIMD-Programmierung Möglichkeiten: • • • • Maschinensprache Inline-Assembling Nutzung von Bibliotheken, z.B. Small Matrix Library für SSE Compiler unterstützen teilweise SIMD-Erweiterungen Intel C++ Compiler ab Version 6: SIMD-Datentypen und Makros für MMX, SSE und SSE2 Vektorisierer für einfache Schleifen GNU C-Compiler: SIMD-Datentypen und Makros für MMX, SSE und 3DNow Peter Sobe 72 x86 Architektur (1) Betrachtet ausgehend vom äußeren Erscheinungsbild: Registersatz: Anzahl der Register, Freiheiten bzw. Beschränkungen bei deren Verwendung Befehlssatz: Befehlsliste und evtl. verschiedene Varianten der Befehle, wenn unterschiedliche Adressierungsarten zugelassen sind. Alles andere betrifft die Implementierung und Realisierung Peter Sobe 73 x86 Architektur (2) Intel 80x86-Familie 80186 8086 Co-Proz. Busbreite Daten/Adress. (Bit) 16/20 8087 80188 80286 80287 80386DX 80386SX 80387 80486DX 80486SX 80487 Pentium Verbesserung der Implementierung der Architektur von 12 CPI beim 8086 auf 1.5 - 3 CPI beim Pentium (CPI = Cycles Per Instruction). Große Bedeutung durch Einsatz in IBM-kompatiblen PCs. Aufgrund der hohen 16/24 Stückzahlen ‚Mainstream’ der 16/24 (SX) derzeitigen 32/32 (DX) Rechnerentwicklung. 32/32 64/32 CISC-Prozessoren aus historischen Gründen „binär abwärtskompatibel“ zum ‚Urahnen’ 8086 Stetige Verbesserung der Technologie (Taktfrequenz von 4.77 bis 10 MHz beim 8086/8088 auf über 3 GHz beim Pentium). Peter Sobe 74 Instruction Set Architecture (Intel IA-32) 8 x 32-Bit-Register mit 16-BitRegistern des 8086 in unteren beiden Bytes 8 x 80-Bit-Gleitkommaregister (internes IEEE-Format) 6 x 16-Bit-Segmentregister (8086: 4): Codesegment CS, Stacksegment SS, 4 Datensegmente DS, ES, FS, GS 32-Bit-Befehlszähler, 32-BitFlagregister (8086: je 16 Bit) Diverse Zusatzregister z. B. für Kontrolle und Ausnahmebehandlung Peter Sobe 75 IA-32 Datentypen CISC-Befehlsformat (variable Länge) Adressierungsarten - unmittelbar - Register indirekt - direkt - indiziert - Register Peter Sobe 76 IA-32 Befehlssatz ohne Gleitkommabefehle Typischer CISC-Befehlssatz Peter Sobe 77 IA-32 Befehlssatz ohne Gleitkommabefehle Peter Sobe 78 Mikroarchitektur Pentium 4 Umsetzung der IA-32 CISC-Befehle in 1 bis 4 Ops (interne RISC-Befehle) durch Decoder. Ausführung in supersklarer RISCArchitektur Trace Execution Cache (TEC) für Ops mit eigener Sprungvorhersage, 3 Ops pro Takt wie PentiumIII Verbesserte Sprungvorhersage für x86Befehle mit größerem BTB 20-stufige I-Pipe, Taktraten bis über 3 GHz 13 Funktionseinheiten, davon max. 6 gleichzeitig aktivierbar 8 KB Datencache (klein, aber schnell); Hardware-Prefetching mit Quad Pumped Speicherschnittstelle (3,2 GByte/s) Befehlssätze (MMX, SSE, SSE2) Optional: Hyperthreading (SMT) Peter Sobe 79 Hyperthreading Intels Implementierung von SMT: 2-fach Hyper-Threading für den P4, auch für Atom CPUs, Verhält sich für das Betriebssystem wie zwei logische Prozessoren, d. h. Multiprozessor-Software ist ohne Änderung lauffähig. P4-Pipeline mit SMT Pipeline-Register (Queues) und einige Pipelinestufen verdoppelt, die meisten Stufen werden abwechselnd von beiden Threads genutzt. Verdoppelung der Register durch RegisterRenaming implementiert. Nur 5% zusätzliche Chipfläche. Konflikte beim Nutzen der gemeinsamen Caches (Cache Aliase) können Leistung einschränken. Peter Sobe 80 x86 Architektur (2) Allgemeine Register AX – Akkumulator-Register, Ziel und Quelle für Rechenoperationen Teilung in hohes Byte (AH) und niedriges Byte (AL) BX - Basis-Register für Anfangsadressen, Teilung in hohes Byte (BH) und niedriges Byte (BL) CX – Count Register, Teilung in hohes Byte (CH) und niedriges Byte (CL), allgemein verwendbar, spezielle Bedeutung bei Schleifen DX - Daten-Register , Teilung in hohes Byte (DH) und niedriges Byte (DL) RAX (bei x86-64) EAX EAX AX AX AH AH 64 Bit Peter Sobe 32 Bit 16 Bit ALAL 8 Bit 81 x86 Architektur (3) Pointer-Register SP Stack-Pointer: zur Adressierung des Stacks verwendet BP Base-Pointer: zur Adressierung des Stacks verwendet IP Instruction-Pointer: Offset des nächsten Befehls Index-Register SI Source-Index: Unterstützung von Adressierungen esi Quelle (eng: source) für Stringoperationen DI Destination-Index: Unterstützung von Adressierungen edi Ziel (eng: destination) für Stringoperationen Segment-Register CS Code-Segment: zeigt auf aktuelles Codesegment DS Daten-Segment: zeigt auf aktuelles Datensegment SS Stack-Segment: zeigt auf aktuelles Stapelsegment ES Extra-Segment: zeigt auf weiteres Datensegment Peter Sobe 82 x86 Architektur (4) Statusflags CF Carry-Flag Übertragflag AF Auxiliary Carry-Flag Hilfsübertragflag ZF Zero-Flag Nullflag SF Sign-Flag Vorzeichenflag PF Parity-Flag Paritätsflag OF Overflow-Flag Überlaufflag Kontrollflags TF Trap-Flag Einzelschrittflag IF Interrupt Enable-Flag Interruptflag Peter Sobe 83 x86 Assembler-Programmierung (1) Die C-Anweisung summe = a + b + c + d; würde beim 80x86 Assembler so aussehen: mov eax,[a] add eax,[b] add eax,[c] add eax,[d] mov [s], eax Mit eax ist das 32 Bit breite AX Register gemeint. Alle Operationen beziehen sich damit auf 32 Bit Verarbeitungsbreite. Peter Sobe 84 x86 Assembler-Programmierung (2) Einfache if-then-else Konstrukte müssen in der AssemblerSprache in Compare und einen bedingten Sprung umgewandelt werden … if (a == 4711) {...} else { ... } Im x86 Assembler sieht das dann so aus: cmp eax,4711 jne ungleich gleich: ... jmp weiter ungleich: ... weiter: ... Peter Sobe 85 x86 Assembler-Programmierung (3) Einfache Zählschleifen werden von einem x86 Prozessor besser unterstützt. Das folgende C-Programm for (i=0; i<100; i++) { summe = summe + a; } sieht im 80x86 Assembler so aus: mov ecx,100 schleife: add eax,[a] loop schleife Der Loop-Befehl dekrementiert implizit das ecx Register und führt den Sprung nur aus, wenn der Inhalt des ecx Registers anschließend nicht 0 ist. Peter Sobe 86 x86 Assembler-Programmierung (4) Speicherzugriff Meistens reichen die Register nicht aus, um ein Problem zu lösen. In diesem Fall muss auf den Hauptspeicher des Computers zugegriffen werden, der erheblich mehr Information speichern kann. Für den Assemblerpogrammierer sieht der Hauptspeicher wie ein riesiges Array von Registern aus, die je nach Wunsch 8, 16 oder 32 Bits "breit" sind (je nach Datentyp). Die kleinste adressierbare Einheit ist ein Byte (= 8 Bits). Um auf einen bestimmten Eintrag des Arrays "Hauptspeicher" zugreifen zu können, muss der Programmierer die Adresse des Eintrages kennen. Das erste Byte des Hauptspeichers bekommt dabei die Adresse 0, das zweite die Adresse 1 usw. Peter Sobe 87 x86 Assembler-Programmierung (5) In einem Assemblerprogramm können Variablen angelegt werden, indem einer Speicheradresse ein Label zugeordnet und dabei Speicherplatz in der gewünschten Größe reserviert wird. [SECTION .data] gruss: db 'hello, world' unglueck: dw 13 million: dd 1000000 [SECTION .text] mov ax,[million] ... db … define byte, dw … define word (2 Bytes), dd … define double word Peter Sobe 88 x86 Assembler-Programmierung (6) Stack Nicht immer will man sich ein neues Label ausdenken, nur um kurzfristig mal den Wert eines Registers zu speichern, beispielsweise, weil man das Register für eine bestimmte Anweisung benötigt, den alten Wert aber nicht verlieren möchte. In diesem Fall wünscht man sich sowas wie einen „Ablagehaufen“. Den bekommt man mit dem Stack. Der Stack ist eigentlich nichts weiter als ein Stück des Hauptspeichers, nur dss dort nicht mit festen Adressen gearbeitet wird, sondern die zu sichernden Daten einfach immer oben drauf geschrieben (push) bzw. von oben heruntergeholt werden (pop). Der Zugriff ist also ganz einfach, vorausgesetzt man erinnert sich daran, in welcher Reihenfolge die Daten auf den Stapel gelegt wurden. Ein spezielles Register, der Stackpointer esp zeigt stets auf das oberste Element des Stacks. Da push und pop immer nur 32 Bits auf einmal transferieren können, ist der Stack in der folgenden Abbildung vier Bytes breit dargestellt. Peter Sobe 89 x86 Assembler-Programmierung (7) Adressierungsarten Die meisten Befehle des x86 können ihre Operanden wahlweise aus Registern, aus dem Speicher oder unmittelbar einer Konstante entnehmen. Beim mov Befehl sind (u. a.) folgende Formen möglich, wobei der erste Operand stets das Ziel und der zweite stets die Quelle der Kopieraktion angeben: Registeradressierung: Der Wert eines Registers wird in ein anderes übertragen. mov ebx,edi Peter Sobe 90 x86 Assembler-Programmierung (8) Unmittelbare Adressierung: Die Konstante wird in das Register übertragen. mov ebx,1000 Direkte Adressierung: Der Wert der an der angegebenen Speicherstelle steht, wird in das Register übertragen. mov ebx,[1000] Register-Indirekte Adressierung: Der Wert, der an der Speicherstelle steht, die durch das zweite Register bezeichnet wird, wird in das erste Register übertragen. mov ebx,[eax] Peter Sobe 91 x86 Assembler-Programmierung (9) Basis-Register Adressierung: Der Wert, der an der Speicherstelle steht, die sich durch die Summe des Inhalts des zweiten Registers und der Konstanten ergibt, wird in das erste Register übertragen. mov eax,[10+esi] Peter Sobe 92 Assembler-Einbindung in C (1) In einem C-Programm kann jede Anweisung durch einen Block von Assembler-Befehlen durch folgende Syntax ersetzt werden: _asm { <Folge von Assembler-Befehlen> } ; Jeder Assemblerbefehl muss durch Semikolon abgeschlossen sein. Die in den Assembler-Befehlen vorkommenden Hauptspeicheroperanden können Bezeichnungen des C-Programms sein. Die interne Darstellung und vor allem die Länge der Operanden muss gemäß der C-Deklaration so sein, dass sie kompatibel zum angewandten Befehl ist. Damit kann ein Datenaustausch zwischen den Assembler- und den C-Passagen erfolgen. Peter Sobe 93 Assembler-Einbindung in C (2) Beispiel: mov buf,cx; Mit cx ist das counter-Register (16 Bit) bezeichnet, folglich muss die angenommene C-Variable buf auch als eine vorzeichenlose Variable mit 16 Bit deklariert sein, d.h. unsigned short buf; Soll dagegen das 32-Bit-counter-Register adressiert werden (ecx): mov buf,ecx; so ist buf folgendermaßen zu deklarieren: unsigned int buf; Wird dies nicht beachtet, treten beim kompilieren Fehler auf. Da der C-Compiler einen Inline-Assembler benutzt, sind nicht alle Codes, wie bei einem eigenständigen Assembler zugelassen. Peter Sobe 94 Assembler-Einbindung in C (3) Beispiel: #include <stdio.h> void main() { unsigned short erg; unsigned short eingabe = 2; unsigned char z; unsigned int buf; _asm { //xor cx,cx; // cx=0 mov cx, eingabe; inc cx; // cx++ inc cx; // cx++ shl cx,3; // *8 mov erg,cx; // erg=cx mov bl,102; // bl='f‘ mov z,bl; // z=bl }; printf("\n erg=%u z=%c \n",erg,z); } Peter Sobe 95