Analýza možností procesorově závislých optimalizací nad binárním

Transkript

Analýza možností procesorově závislých optimalizací nad binárním
Univerzita Karlova v Praze
Matematicko-fyzikální fakulta
DIPLOMOVÁ PRÁCE
Petr Panuška
Analýza možností procesorově závislých optimalizací
nad binárním kódem a implementace optimalizátoru
pro konkrétní procesorovou architekturu
Katedra softwarového inženýrství MFF UK
Vedoucí diplomové práce: Mgr. Petr Merta
Studijní program: Informatika
Chtěl bych poděkovat svým rodičům, sourozencům a přítelkyni za trpělivost, kterou mi
věnovali během psaní této práce. Dále chci poděkovat Mgr. Petru Mertovi za vedení diplomové
práce, za jeho rady, připomínky a schvalování. Dále děkuji ostatním členům Ulity, jež mi
pomáhali s literaturou a radami, především Mgr. Davidovi Bednárkovi. Další díky jistě patří
Robertu Muthovi za jeho e-mailové konzultace i RNDr. Ing. Jiřímu Peterkovi a Jakubovi
Jelínkovi za odkazy na zdroje literatury.
Prohlašuji, že jsem svou diplomovou práci napsal samostatně a výhradně s použitím
citovaných pramenů. Souhlasím se zapůjčováním práce.
V Praze dne 16. dubna 2001
Petr Panuška
Analýza možností procesorově závislých optimalizací nad binárním kódem a implementace
optimalizátoru pro konkrétní procesorovou architekturu
Obsah
1. Úvod ............................................................................................................................ 1
2. Související práce.......................................................................................................... 3
3. Analýza možností optimalizace binárního kódu ......................................................... 4
3.1.
Vlastnosti procesorů ............................................................................................ 4
3.2.
Konstrukty vyšších jazyků ................................................................................ 10
3.3.
Formát vstupního souboru ................................................................................. 11
3.4.
Analýza binárního kódu .................................................................................... 12
3.4.1. Zavedení některých pojmů ........................................................................... 13
3.4.2. Statická analýza binárního kódu................................................................... 14
3.4.3. Problematika statické analýzy binárního kódu............................................. 16
3.4.4. Předpoklady korektní analýzy i optimalizace binárního kódu ..................... 18
3.4.5. Dynamická analýza binárního kódu ............................................................. 20
3.5.
Optimalizace kódu............................................................................................. 21
4. Vnitřní architektura procesorů IA-32 ........................................................................ 25
4.1.
Intel 80386......................................................................................................... 25
4.2.
Intel 80486......................................................................................................... 25
4.3.
Intel Pentium ..................................................................................................... 26
4.4.
Intel Pentium MMX .......................................................................................... 34
4.5.
Intel Pentium Pro ............................................................................................... 36
4.6.
Shrnutí ............................................................................................................... 42
5. Procesorově závislé optimalizace kódu procesorů IA-32 architektury P5 a P6........ 44
5.1.
Vhodné zarovnávání .......................................................................................... 44
5.1.1. Zarovnávání dat ............................................................................................ 44
5.1.2. Zarovnávání kódu ......................................................................................... 45
5.2.
Optimální využití vyrovnávací paměti .............................................................. 45
5.3.
Změna pořadí instrukcí...................................................................................... 46
5.3.1. Eliminace prodlení při výpočtu adresy ......................................................... 46
5.3.2. Eliminace imperfektního párování ............................................................... 47
5.3.3. Zvýšení propustnosti dekodéru..................................................................... 47
5.3.4. Podmínky pro přerovnání kódu .................................................................... 48
5.4.
Dělení komplexních instrukcí............................................................................ 48
5.4.1. Méně cyklů potřebných k vykonání instrukce.............................................. 48
5.4.2. Získání párovatelnosti instrukcí.................................................................... 49
5.4.3. Zmenšení počtu generovaných mikrooperací............................................... 50
5.4.4. Shrnutí........................................................................................................... 50
5.5.
Náhrada instrukcí............................................................................................... 50
5.5.1. Záměna ekvivalentních instrukcí.................................................................. 50
5.5.2. Lepší využitelnost exekučních jednotek....................................................... 51
5.6.
Odstranění redundantních instrukcí................................................................... 52
5.7.
Další techniky optimalizace .............................................................................. 53
5.7.1. Eliminace prodlení při zápisu do částečného registru .................................. 53
5.7.2. Eliminace závislostí mezi instrukcemi ......................................................... 53
5.7.3. Použití instrukce LEA ................................................................................... 54
6. Závěr.......................................................................................................................... 55
7. Literatura ................................................................................................................... 56
i
Analýza možností procesorově závislých optimalizací nad binárním kódem a implementace
optimalizátoru pro konkrétní procesorovou architekturu
Dodatek A - Program SCOUT.............................................................................................. I
A.1. Uživatelská dokumentace ..................................................................................... I
A.1.1. Ovládání programu ......................................................................................... I
A.1.2. Činnost programu ..........................................................................................II
A.1.3. Výstupy programu ....................................................................................... III
A.2. Technická dokumentace .................................................................................... IV
A.3. Měření výsledků programu SCOUT ................................................................. IX
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci ................................................. XI
B.1. Historie a vývoj procesorů Intel ........................................................................ XI
B.2. Procesor Intel 8086...........................................................................................XII
B.3. Procesor Intel 80186...................................................................................... XVII
B.4. Procesor Intel 80286...................................................................................... XVII
B.4.1. Reálný režim ............................................................................................ XVII
B.4.2. Chráněný režim .......................................................................................XVIII
B.5. Procesor Intel 80386.................................................................................... XXIV
B.5.1. Reálný režim .......................................................................................... XXIV
B.5.2. Chráněný režim ...................................................................................... XXVI
B.6. Procesor Intel 80486..................................................................................XXVIII
B.7. Procesor Intel Pentium ..............................................................................XXVIII
B.8. Procesor Intel Pentium MMX ..................................................................... XXIX
B.9. Procesor Intel Pentium Pro.......................................................................... XXIX
B.10.
Procesory Intel Pentium II, III, IV........................................................... XXIX
B.11.
Konkurenční procesory ........................................................................... XXIX
Dodatek C - Slovníček pojmů........................................................................................XXX
Seznam algoritmů
alg. 1 – statická analýza binárního kódu ....................................................................................... 14
alg. 2 – redukce instrukcí a optimalizace jejich operandů ............................................................ 24
alg. 3 – rozpoznání konstruktu switch..........................................................................................VII
alg. 4 – optimalizace svazků instrukcí a expanze operandů u instrukcí větvení ..........................VII
alg. 5 – přesměrování a kontrakce relativních cílových adres instrukcí větvení.........................VIII
alg. 6 – setřesení optimalizovaných kopií svazků instrukcí ........................................................VIII
alg. 7 – přesměrování cílových adres instrukcí větvení původních svazků ................................VIII
Seznam obrázků
obrázek 1 – duplikace kódu........................................................................................................... 19
obrázek 2 – proudové zpracování instrukce .................................................................................. 25
obrázek 3 – změna stavu hodnoty záznamu na základě skutečného provedení skoku.................. 32
obrázek 4 – model procesoru Intel Pentium Pro (architektura P6) ............................................... 37
obrázek 5 – přepočet efektivní adresy na fyzickou ......................................................................XII
obrázek 6 – registry procesoru Intel 8086 ...................................................................................XIII
obrázek 7 – převod efektivní adresy na lineární ve chráněném módu procesoru ...................... XIX
obrázek 8 – převod lineární adresy na fyzickou pomocí mechanismu stránkování ............... XXVII
Seznam tabulek
tab. 1 – zarovnání dat na architektuře P5 ...................................................................................... 27
tab. 2 – instrukce procesoru Intel Pentium párovatelné v libovolné lince .................................... 28
ii
Analýza možností procesorově závislých optimalizací nad binárním kódem a implementace
optimalizátoru pro konkrétní procesorovou architekturu
tab. 3 – instrukce procesoru Intel Pentium párovatelné pouze v lince u ....................................... 28
tab. 4 – instrukce procesoru Intel Pentium párovatelné pouze v lince v ....................................... 29
tab. 5 – počet cyklů nutných na provedení dvou párujících instrukcí........................................... 31
tab. 6 – perfektně predikované vzory historie ............................................................................... 35
tab. 7 – zarovnání dat na architektuře P6 ...................................................................................... 36
tab. 8 – vlastnosti procesorů Intel Pentium a vyšších.................................................................... 42
tab. 9 – (skoro) ekvivalentní instrukce .......................................................................................... 51
tab. 10 – parametry programu SCOUT ............................................................................................ I
tab. 11 – výsledky analýzy programu SCOUT u vybraných příkladů .......................................... IX
tab. 12 – výsledky optimalizace programu SCOUT u vybraných příkladů ....................................X
tab. 13 – možnosti adresace procesoru Intel 8086.......................................................................XIII
tab. 14 – rozložení příznaků u procesoru Intel 8086 ....................................................................XV
tab. 15 – seznam přerušení procesoru Intel 8086 ....................................................................... XVI
tab. 16 – stavový segment úlohy procesoru Intel 80286 ............................................................ XXI
tab. 17 – příznaky procesoru Intel 80286 .................................................................................XXIII
tab. 18 – seznam přerušení procesoru Intel 80286 ...................................................................XXIII
tab. 19 – prefixy instrukcí procesorů IA-16 a IA-32 .................................................................XXV
Seznam příkladů
příklad 1 – použití predikátových registrů na IA-64 ....................................................................... 8
příklad 2 – konstrukt switch .......................................................................................................... 15
příklad 3 – větvení na procesoru SPARC...................................................................................... 16
příklad 4 – maskující se virus........................................................................................................ 17
příklad 5 – šifrování kódu.............................................................................................................. 17
příklad 6 – chybná interpretace podmíněného skoku .................................................................... 17
příklad 7 – relativní adresování paměti ......................................................................................... 18
příklad 8 – použití kódu v datovém operandu ............................................................................... 18
příklad 9 – přepis předzpracované instrukce ................................................................................. 21
příklad 10 – typický kód aplikačního programu............................................................................ 22
příklad 11 – optimalizovatelný kód............................................................................................... 22
příklad 12 – optimalizovaný kód................................................................................................... 22
příklad 13 – lépe optimalizovaný kód ........................................................................................... 23
příklad 14 – seřazení možných operandů dle optimality jejich použití......................................... 23
příklad 15 – případy párování a nepárování instrukcí procesoru Intel Pentium ........................... 29
příklad 16 – zacházení s 16-bitovými a 8-bitovými registry jako s 32-bitovými.......................... 29
příklad 17 – párování instrukcí, které čtou z a zapisují do registru EFLAGS .............................. 29
příklad 18 – instrukce se dvěma konstantami v jejich kódování se nepárují ................................ 29
příklad 19 – instrukce s bázovým i index. registrem v adresaci operandu nepáruje ..................... 30
příklad 20 – výskyt prodlení při výpočtu adresy ........................................................................... 30
příklad 21 – imperfektní párování při přístupu do stejného dvojslova v paměti........................... 31
příklad 22 – imperfektní párování při kolizi adres ve vyrovnávací paměti................................... 31
příklad 23 – počet cyklů při provedení kombinace dvou R/M a R/M/W instrukcí....................... 31
příklad 24 – přidělení záznamu instrukci provedené v lince u předcházejícího páru ................... 32
příklad 25 – následky po sobě jdoucích instrukcí větvení............................................................. 33
příklad 26 – více BTB záznamů přidělených jediné instrukci větvení.......................................... 33
příklad 27 – sdílení stejného BTB záznamu na procesoru Intel Pentium MMX........................... 35
iii
Analýza možností procesorově závislých optimalizací nad binárním kódem a implementace
optimalizátoru pro konkrétní procesorovou architekturu
příklad 28 – nezávislé části kódu................................................................................................... 38
příklad 29 – čtení příliš mnoha registrů architektury .................................................................... 38
příklad 30 – vznik prodlení při zápisu do části registru ................................................................ 39
příklad 31 – eliminace prodlení při zápisu do celého registru ...................................................... 40
příklad 32 – výskyt prodlení při zápisu a čtení registru příznaků ................................................. 40
příklad 33 – další příklady, kdy dojde k prodlení a kdy nikoli ..................................................... 41
příklad 34 – výskyt prodlení po instrukci posunu či rotace .......................................................... 41
příklad 35 – prodlení při kombinaci různě velkých operandů na stejné adrese paměti ................ 42
příklad 36 – zarovnání lokálních dat ............................................................................................. 44
příklad 37 – prázdné instrukce ...................................................................................................... 45
příklad 38 – čtení dat sdílejících stejný řádek ve vyrovnávací paměti.......................................... 46
příklad 39 – eliminace prodlení..................................................................................................... 46
příklad 40 – imperfektní párování z důvodu přístupu na stejnou adresu ...................................... 47
příklad 41 – imperfektní párování z důvodu výskytu prodlení ..................................................... 47
příklad 42 – dekódování 4 instrukcí ve 3 cyklech (P6) ................................................................. 47
příklad 43 – rozdělení instrukcí do sekvence optimálnějších instrukcí......................................... 49
příklad 44 – rozdělení instrukce do instrukcí, které neprodukují identické výsledky................... 49
příklad 45 – rozdělení instrukce do více instrukcí, které mohou být optimálnější ....................... 49
příklad 46 – menší počet generovaných mikrooperací.................................................................. 50
příklad 47 – záměna ekvivalentních instrukcí ............................................................................... 51
příklad 48 – provádění mikrooperací v exekučních jednotkách.................................................... 52
příklad 49 – redundantní instrukce v binárním kódu..................................................................... 52
příklad 50 – eliminace prodlení použitím instrukce MOVZX ........................................................... 53
příklad 51 – eliminace prodlení použitím instrukce XOR nebo SUB ............................................... 53
příklad 52 – závislosti mezi instrukcemi ....................................................................................... 54
příklad 53 – použití instrukce LEA ................................................................................................. 54
příklad 54 – nastavení příznaku krokování ..................................................................................XV
příklad 55 – korektní nastavení adresy zásobníku...................................................................... XVI
iv
Úvod
1. Úvod
Již před třiceti lety predikoval pan Moor, jeden ze zakladatelů firmy Intel, svůj, tzv.
Moorův, zákon, který praví, že každých 18 měsíců se výkon nového počítače oproti jeho
předchůdcům zdvojnásobí při zachování ceny. Toto pravidlo platí víceméně dodnes. Vývoj
procesorů, jejichž výkon ovlivňuje rychlost počítače více než jakákoliv jiná část, je natolik
intenzivní, že vývoj SW na něj reaguje opožděně a mnoho dosud používaného staršího SW tak
nevyužívá všech výhod modernějších procesorů. Dalším důvodem nevyužívání nových rysů
procesorů je tendence výrobců SW dodávat programy pro širší trh a ty tak nebývají překládány
optimálně vzhledem k nejmodernějším procesorům. Nabízí se zde otázka, zda-li je možné
vyvinout nástroj, jehož aplikací na již nainstalovaný neoptimální SW se tento „vylepší“ a to
s ohledem na vlastnosti procesoru počítače, kde je SW instalován.
Předkládaná práce se pokouší odpovědět na několik otázek – zda-li je takový nástroj
možné vyvinout a pokud ano, tak za jakých podmínek, a pokud případně ne, tak které okolnosti
jeho vývoji zabraňují. Nároky na tento nástroj jsou zřejmé. Je třeba, aby aplikace nástroje
proběhla nad spustitelným binárním kódem v takové podobě, v jaké je dodáván výrobci, nástroj
by neměl využívat těch vstupních informací, které nejsou součástí standardního binárního
souboru. Bylo by dobré, kdyby nástroj vyžadoval žádnou či pouze minimální asistenci obsluhy, a
samozřejmě je třeba, aby byla zachována funkčnost programu a dosaženo jeho vylepšení.
Práce se snaží nalézt odpověď na položené otázky následujícím postupem. Je
nepochybné, že vlastnosti a možnosti, které konkrétní procesor nabízí, značně ovlivňují hledané
odpovědi, proto jsou zde vyjmenovány a srovnány některé základní vlastnosti několika běžných
procesorů. Dalším faktorem, který ovlivňuje možnosti optimalizace, je formát binárních souborů,
neboli, které informace a jakým způsobem jsou v souboru zakódovány. Opět je zde provedeno
srovnání a jmenování běžných informací uložených v binárním souboru. V neposlední řadě
ovlivňují možné optimalizace obvyklé způsoby, kterými jsou překládány konstrukty vyšších
jazyků do strojového jazyka procesoru. Případně to jsou i možnosti, které nabízí kód psaný ve
strojovém jazyce procesoru. Proto jsou zde uvedeny některé konstrukty vyšších jazyků se
způsobem jejich přeložení a vypsáno několik příkladů psaných ve strojovém kódu, které mohou
negativně ovlivnit možnosti optimalizace binárního kódu.
Práce se snaží odpověď nalézt takovým způsobem, aby bylo nutno konkretizovat
použitelné procesory, formáty binárních souborů, překladače apod. co nejméně. Je zřejmé, že
striktní dodržení podmínky být „nekonkrétní“ značně ztěžuje hledání odpovědí a zmenšuje šance
na jejich nalezení. Z tohoto důvodu je první část práce psána obecně nebo s odkazy na širší
skupinu procesorů, binárních souborů, překladačů... Druhá část práce pak konkretizuje popsané
postupy na jediný procesor a operační platformu, této platformy se týká většina uváděných
příkladů. Pro aplikaci nalezených závěrů byl vybrán 32-bitový procesor Intel architektury P5 a
P6 na OS Windows 95/98/NT/2000. Příklady byly překládány překladačem Microsoft Visual
C++ 6.0, Borland C 5.0 či GNU CC 2.7.2.1, případně byly psány přímo ve strojovém kódu a
přeloženy Turbo Assemblerem 3.2. Důvodem pro volbu procesoru Intel je jeho snadná
dostupnost, velká rozšířenost a v neposlední řadě i značná složitost slibující velkou množinu
možných optimalizací. OS Windows byl vybrán nejen pro svoji rozšířenost, ale také proto, že
používání vyvíjeného nástroje u jiného obvyklého OS, který se nabízí v případě procesoru Intel,
totiž OS Linux, jaksi ztrácí význam. Většina SW pro OS Linux je dostupná ve formě zdrojových
kódů.
1
Úvod
Všechny hledané odpovědi jsou značně komplikované a obsáhlé, z prostorových důvodů
je spousta detailů potlačena. Texty jsou doplněny mnoha odkazy na další zdroje, odkud bylo
čerpáno při psaní této práce.
Před další prací s dokumentem je vhodné prostudovat slovníček zavedených pojmů (viz
Dodatek C - Slovníček pojmů), některé termíny, používané i v jiných situacích, mohou být
pro svoji nejednoznačnost zavádějící. Ne všechny použité pojmy jsou explicitně zavedeny a
v uvedených popisech pojmů není dodržen přísně formalizovaný postup. Pro pojmenovávání
odborných termínů je raději volen zažitý český překlad1, při prvním použití některých termínů je
v závorce za ním uveden anglický originál. Hlavním zdrojem inspirace v použité české
terminologii byla skripta [JIRO00].
1
i když zde existují výjimky, např. je používán anglický výraz „byte“ namísto českého „slabika“
2
Související práce
2. Související práce
Přímo s problematikou procesorově závislých optimalizací spustitelného binárního kódu
se nezaobírá žádná známá publikace. Existují však práce zabývající se analýzou binárního kódu a
jeho překladem na jinou procesorovou jednotku, či do vyššího jazyka (např. jazyka C).
Projekt ALTO (A Link Time Optimizer) z roku 1999 od Roberta Mutha [MUTH99]
provádí optimalizace binárního kódu a je implementován pro Digital Unix na procesoru Alpha.
Část projektu, zabývající se analýzou kódu, je v podstatě stejná jako analýza u programu
SCOUT (Simple binary Code analysing and Optimizing UTility)2, pouze se zabývá lepší
diagnostikou toku programu i přenosu hodnot při volání procedur. ALTO navíc s úspěchem
využívá vlastností RISC-ového procesoru3 při analýze kódu, dále má svoji práci usnadněnu tím,
že optimalizaci neprovádí na spustitelných souborech, ale na souborech připravených
ke spojování (Linking) (tzv. cílové soubory). Optimalizační část projektu ALTO se dost liší od
programu SCOUT, především z hlediska použitých optimalizací.
Program DCC od Cristiny Cifuentes se snaží dekompilovat předložený binární kód4 a
sestavit k němu původní zdrojový kód v jazyce C. Tento program byl zhotoven jako součást
doktorandské práce Reverse Compilation Techniques [CIFU94], která obsahuje řadu
zajímavých poznatků, některé z nich jsou použity i v této práci5.
Existují i další práce, např. ty, které jsou založené na dynamické analýze binárního kódu.
Dynamická analýza binárního kódu se liší od statické analýzy v tom, že pro vlastní analýzu je
třeba program spustit. Během jeho vykonávání se analyzátor pokouší zjistit tok programu, tok
dat, apod.
2
SCOUT je implementace analyzátoru a optimalizátoru využívajících poznatků popsaných v této práci;
SCOUT je součástí předkládaného materiálu
3
např. bere v potaz to, že na procesoru Alpha jsou všechny instrukce stejně dlouhé a začínají vždy
na adresách dělitelných velikostí instrukce
4
pro platformu MS-DOS na procesorech IA-16
5
např. duplikace kódu při dosažení pouze částečné analýzy, viz odstavec „Duplikace kódu“ kapitoly 3.4.4
3
Analýza možností optimalizace binárního kódu
3. Analýza možností optimalizace binárního kódu
Možných optimalizací binárního kódu je celá řada. V zásadě se jednotlivé optimalizace
dají rozdělit do dvou skupin. V první skupině jsou ty optimalizace, které binární kód zrychlují,
v druhé skupině jsou optimalizace, které kód zkracují6, tyto dvě skupiny nejsou obecně
disjunktní.
Dále se optimalizace mohou dělit podle toho, zda-li se zaměří na instrukce kódu nebo
na data, se kterými instrukce pracují. Navíc optimalizace úzce souvisí s konkrétní architekturou
procesoru, na různé typy procesorů jsou vhodné různé optimalizace. Někdy je dokonce možné, že
konkrétní typ optimalizace vhodný pro jeden typ procesoru bude na jiném typu procesoru
optimálnost kódu zhoršovat.
3.1.
Vlastnosti procesorů
Možnosti optimalizace binárního kódu ovlivňuje prakticky jakákoli vlastnost procesoru,
před analýzou možných optimalizací je tedy třeba mít detailní informace o architektuře
procesoru. Popsání vlastností nelze z prostorových důvodů uvést kompletně, proto je zde uveden
popis jen několika oblastí procesorové architektury a způsoby jejich implementace v konkrétním
typu procesoru7. Jsou zde popsány
• obecné registry, které se používají pro většinu aritmeticko-logických operací v programech a
značně ovlivňují všechny další jeho vlastnosti,
• kódování instrukcí, které má vliv na optimalizační techniky a možnou analýzu kódu,
• instrukce větvení, jejichž způsoby použití určují možnosti analýzy binárního kódu a
• predikce instrukcí větvení, které ovlivňují rychlost vykonávaného kódu.
Ačkoli mohou být různé procesory vzájemně nekompatibilní a v mnohém odlišné, lze
v jejich architektuře vysledovat mnoho společných vlastností. Způsoby implementace výše
uvedených rysů procesorů jsou ukázány na procesorech Alpha8, SPARC-V9 (Scalable Processor
ARCitecture Version 9)9, IA-6410, IA-1611, IA-3211 a Motorola 680x0. Pro srovnání jsou uváděny
i vlastnosti virtuálního stroje Javy (JVM – Java Virtual Machine), který se od obvyklých
procesorů značně liší. Obecný úvod do problematiky procesorů je popsán v [JIRO00].
Obecné registry
Procesor Alpha obsahuje 32 64-bitových obecných celočíselných registrů (R0 až R31),
registr R31 má speciální význam. Zjednodušeně lze říci, že pokud vystupuje registr R31 na místě
zdrojového operandu, obsahuje vždy hodnotu 0, pokud vystupuje na místě cílového registru, jsou
výsledky operace (nebo alespoň některé výsledky) skartovány. Obecné registry mohou
6
což nemusí nutně vést k jeho zrychlení
popis procesorů je v celé práci navíc zúžen na práci s celočíselnou aritmetikou, není popisována podpora
počítání s necelými čísly, ani, v poslední době se hojně objevující, instrukce multimediálního rozšíření
8
Alpha, SPARC-V9 i IA-64 jsou „pouze“ návrhy procesorových architektur, jejich konkrétní
implementace se mohou lišit u přesně definovaných vlastností v závislosti na výrobci daného procesoru
9
jeho předchůdcem je 32-bitový procesor SPARC-V8, viz [SPRC92]
10
procesor IA-64 je v některých pracovních režimech kompatibilní s IA-32, zde je pod pojmem IA-64
myšlena ta část procesoru IA-64, kterou IA-32 neobsahuje
11
tímto pojmenováním jsou označeny všechny 16-bitové a 32-bitové procesory řady Intel 80x86 a Intel
Pentium
7
4
Analýza možností optimalizace binárního kódu
vystupovat jak na místě argumentů instrukcí (obsahují data), tak při adresování paměti (obsahují
virtuální adresu v paměti).
Podobný přístup mají i procesory SPARC-V9 či IA-64, s tím rozdílem, že obsahují jiný
počet registrů. IA-64 obsahuje 32 statických registrů (Static General Register) a 96
zásobníkových registrů (Stacked General Register). Zásobníkové registry slouží k ukládání
lokálních proměnných a IA-64 obsahuje mechanismus, kdy mohou být při volání podprocedury
původní hodnoty zásobníkových registrů mapovány na zásobník a tím uvolněny pro volanou
podproceduru, podrobněji viz odstavec „Volání vnořené funkce či metody“ kapitoly 3.2.
Instrukce procesoru SPARC-V9 mohou pracovat s 32 64-bitovými obecnými registry,
které se dělí na 8 globálních registrů (g0 až g7), 8 výstupních registrů (o0 až o7), 8 lokálních
registrů (l0 až l7) a 8 vstupních registrů (i0 až i7). Skutečný počet registrů architektury (r[0]
až r[63-528]) je však závislý na implementaci. Globální registry jsou vždy uloženy v registrech
r[0] až r[7], zbylé jsou mapovány v závislosti na nastaveném okně (Register Window).
Procesor obsahuje instrukce pro posun okna, okno lze posunout o násobek 16. Výstupní registry
jednoho okna jsou tak mapovány do vstupních registrů následujícího a naopak, to je použitelné
pro implementaci předávání parametrů při volání podprocedur.
Registry procesorů IA-16 a IA-32 jsou popsány v odstavcích „Registry“ kapitol B.2 a
B.5.1 dodatku B. Mikroprocesor Motorola obsahuje 16 obecných 32-bitových datových registrů,
které se dělí na 8 datových (D0 až D7) a 8 adresových registrů (A0 až A7). Adresový registr A7,
který slouží jako ukazatel zásobníku, je navíc zdvojen pro použití zvlášť v uživatelském a
privilegovaném (systémovém) módu. Všechny datové registry a adresové registry, kromě registru
12
A7 jsou důsledně ortogonální , navíc je počet instrukcí, které porušují ortogonalitu registru A7
minimální (prakticky jen volání podprogramu). S dolními polovinami a dolními čtvrtinami
datových registrů lze pracovat jako s 16-bitovými a 8-bitovými registry. Do adresových registrů
lze také zapisovat 16-bitové hodnoty, ty jsou však vždy znaménkově rozšířeny a je změněno
všech 32-bitů adresového registru. Naprostá většina instrukcí umožňuje používat kterýkoli
datový i adresový registr.
JVM neobsahuje obecné registry, místo toho implementuje zásobník operandů (Operand
Stack). Většina instrukcí JVM pracuje pouze s vrchními položkami na zásobníku, typicky
instrukce odebere ze zásobníku zdrojové operandy, provede operaci a uloží na zásobník
výsledek. Zásobník operandů je alokován pro každou metodu zvlášť.
Platí konstatování, že vyšší počet registrů v procesoru znamená obecně menší nutnost
přístupu do paměti aplikačním programem a tudíž i vyšší rychlost prováděného kódu. Větší počet
registrů navíc zvyšuje pravděpodobnost nalezení volného registru během některých optimalizací
binárního kódu. Důsledná ortogonalita registrů, kdy lze snadno zaměňovat různé varianty
instrukcí, ulehčuje implementaci algoritmů optimalizace. Procesor s vyšším počtem obecných
ortogonálních registrů má lepší výchozí pozici pro dosažení optimálního kódu než procesor
bez těchto vlastností.
Kódování instrukcí
Všechny instrukce procesoru Alpha (i SPARC-V9) jsou dlouhé 32-bitů a leží vždy
na adresách dělitelných čtyřmi. Všech 32 bitů lze rozdělit do několika polí různého počtu a
různých velikostí, instrukce se pak dělí do pěti skupin dle toho, která pole instrukce v sobě
obsahuje (instrukce práce s pamětí, větvení, celočíselné aritmeticko-logické, neceločíselné
instrukce a PAL instrukce). Horních 6 bitů je vždy operační kód instrukce, mohou zde být pole
12
pokud lze některý registr použít na místě operandu některé instrukce, lze použít i libovolný jiný registr
5
Analýza možností optimalizace binárního kódu
pro uložení jednoho či dvou zdrojových registrů a jednoho cílového registru (5 bitů), dále pole
pro přímý paměťový operand (16 bitů), či pole pro relativní posunutí instrukce větvení (21 bitů),
a další.
Procesor IA-64 kóduje vždy tři instrukce ve 128 bitech. Tyto tři instrukce se nazývají
balík (Bundle), každá instrukce je v rámci balíku kódována 41 bity včetně operandů a zbylých 5
pomocných bitů (Template Bits) má zvláštní význam pro celý balík. Jedinou výjimku tvoří
instrukce MOVL, která umožňuje přesunout konstantu o velikosti 64 bitů do obecného registru, tato
instrukce zabírá 82 bitů a musí být uložena ve dvou následujících instrukcích jednoho balíku.
Každá instrukce procesoru IA-64 spadá do jedné množiny dle svého charakteru –
množina celočíselných aritmetických instrukcí, množina celočíselných nearitmetických instrukcí,
množina neceločíselných instrukcí, množina instrukcí pracujících s pamětí, množina instrukcí
větvení a množina rozšířených instrukcí. Instrukce z příslušné množiny lze spouštět vždy jen
v jedné (kromě celočíselných aritmetických instrukcí – ty lze spouštět ve dvou) ze čtyř
exekučních jednotek (Execution Unit). Pomocné bity v balíku určují, ve které exekuční jednotce
se daná instrukce provede.
Instrukce procesoru IA-64 používají zdroje (Resources) – typicky registry. Pokud
sousedící instrukce používají stejné zdroje, označuje se tato situace jako závislost zdrojů.
Závislost zdrojů musí být indikováno procesoru v pomocných bitech balíku.
Instrukce procesoru IA-16 a IA-32 jsou ve zkratce popsány v odstavcích „Instrukce“
kapitol B.2, B.3, B.5.1 a B.7 dodatku B, podrobnější popis lze nalézt v [INTL99a], [CRVV92],
[CADA91], [VAGN94] a v dalších.
Instrukce procesoru Motorola začínají vždy na sudých adresách, jejich velikost je
proměnlivá13 a vždy dělitelná dvěmi. První dva byty instrukce (Simple Effective Adress
Operation Word) definují její velikost, adresovací mód a prováděnou operaci. Další byty, pokud
jsou přítomné, přesněji specifikují instrukci a její operandy (přímé operandy, relativní posunutí
instrukcí větvení, konstanty, rozšíření adresovacího módu atd.)
Instrukce JVM se skládají vždy z operačního kódu dlouhého jeden byte a operandů, které
bezprostředně následují. Počet operandů je určen operačním kódem instrukce a může být nulový.
Mnoho instrukcí pracuje pouze se zásobníkem operandů a tudíž nemají žádné explicitně uvedené
operandy. Existují instrukce pro uložení hodnot lokálních proměnných či konstant na zásobník
operandů a pro vyzvednutí hodnoty zpět do lokální proměnné. Některé instrukce jsou velmi
komplexní a velikost jejich kódování (včetně operandů) není pevně stanovena.
Shodná velikost všech instrukcí procesoru a určení násobku adres, na který jsou instrukce
zarovnány, pomáhá rozpoznávat jednotlivé instrukce. Tím se urychluje jejich vykonávání
procesorem i usnadňuje optimalizace (resp. analýza) binárního kódu.
Instrukce větvení a jejich predikce
Procesor Alpha obsahuje instrukce podmíněného a nepodmíněného větvení. Instrukce
podmíněného větvení (Bcc) obsahuje vždy přímý znaménkový operand o velikosti 21 bitů.
Při splněné podmínce je přímý operand vynásoben 4, rozšířen na 64 bitů a přičten k registru PC
(Program Counter), jinak zůstává registr PC nezměněn. Podmíněné instrukce větvení jsou
predikovány dle znaménka přímého operandu, pokud je přímý operand záporný, je predikováno,
že ke skoku dojde, pokud je operand nezáporný, je predikováno, že ke skoku nedojde.
Procesor Alpha dále obsahuje instrukce BR (Unconditional Branch), BSR (Branch to
Subroutine), JMP (Jump), JSR (Jump to Subroutine), RET (Return from Subroutine) a JSR13
instrukce má velikost minimálně 2 byty, může mít však až 22 bytů
6
Analýza možností optimalizace binárního kódu
COROUTINE (Jump to Subroutine Return) nepodmíněného skoku, u všech těchto instrukcí lze
specifikovat obecný celočíselný registr, do kterého se uloží návratová adresa (adresa následující
instrukce). BR a BSR obsahují přímý znaménkový operand o velikosti 21 bitů relativně určující
cílovou adresu větvení, cílová adresa ostatních instrukcí větvení je uložena ve specifikovaném
obecném registru (může být stejný jako ten, který obsahuje návratovou adresu).
K predikci nepodmíněných instrukcí větvení procesor obsahuje zásobník návratových
adres (RAS – Return-Address Stack), do kterého je uložena adresa následující instrukce
v případě instrukcí použitých pro skok do podprocedury a odkud je tato adresa vyzvednuta ve
chvíli návratu z procedury. Pro správnou funkci predikce je třeba, aby pro každou instrukci skoku
do podprocedury existovala instrukce návratu z podprocedury, která skočí na adresu následující
za instrukcí skoku do podprocedury.
Instrukce BR a BSR provedou stejnou operaci s tím rozdílem, že BR je predikována jako
instrukce větvení bez návratu, kdežto BSR je predikována jako instrukce skoku do podprocedury.
Podobně to platí pro instrukce JMP, JSR, RET a JSR-COROUTINE, opět je mezi nimi rozdíl jen
ve způsobu jejich predikce. Instrukce JMP a JSR dále obsahují 14-bitový přímý operand, který
slouží k predikci prováděného skoku, a který může obsahovat relativní adresu, kam je skok veden
(skok je veden na absolutní adresu uvedenou v obecném registru). Predikční mechanismus
u instrukce JSR uloží adresu následující instrukce do RAS, u instrukce RET vyzvedne adresu
z RAS a použije k predikci a u instrukce JSR-COROUTINE vyzvedne adresu a uloží novou.
Instrukce CALL (Call and Link) procesoru SPARC-V9 obsahuje 30-bitovou relativní
adresu, na kterou provede nepodmíněný skok, adresa instrukce se zároveň uloží do registru o7.
Nepodmíněná instrukce JMPL (Jump and Link) skočí na absolutní adresu spočtenou jako součet
dvou registrů či registru a 13-bitové konstanty, adresa instrukce se uloží do specifikovaného
registru. Procesor vždy vykoná i instrukci bezprostředně následující za instrukcí CALL či JMPL.
Pokud se instrukce CALL či JMPL použije pro volání procedury, je třeba pro návrat z ní přičíst
k uložené adrese 8 bytů (součet délek instrukce větvení a následující instrukce).
Procesor obsahuje podmíněné instrukce BPcc14 (Branch on Integer Condition Codes with
Prediction) a BRcc14 (Branch on Integer Register with Prediction). Rozhodnutí o provedení
podmíněného skoku je u instrukcí BPcc dáno hodnotami osmi15 podmínkových bitů, které byly
nastaveny předchozími aritmeticko-logickými instrukcemi, u instrukcí BRcc je rozhodnutí dáno
výsledkem porovnání zdrojového registru s nulou. U podmíněných instrukcí BPcc lze
nastavením 29. bitu v kódování instrukce (Annul Bit) specifikovat, zda-li se následující instrukce
má provést či nikoli16.
Podmíněné instrukce větvení obsahují predikční bit (Predict Bit), nastavený bit stanovuje
předpoklad, že k větvení dojde, vynulovaný, že nedojde. Implementace procesoru SPARC-V9
od firmy Sun Microsystems – UltraSPARC-IIi obsahuje dynamickou predikci skoků založenou
na podobném principu jako u procesorů IA-32 architektury P5 (viz odstavec „Predikce skoků“
kapitoly 4.3). Podmíněným instrukcím větvení je přiřazen 4-stavový čítač, jehož hodnota se
14
SPARC-V9 obsahuje i podmíněné instrukce Bcc a Bicc (kvůli zpětné kompatibilitě s procesorem
SPARC-V8), které však nemají možnost predikce; jsou označeny jako zastaralé (Deprecated)
15
SPARC-V8 má pouze 4 podmínkové bity (icc – Integer Condition Codes), jejich hodnoty se řídí dle
poslední provedené aritmeticko-logické instrukce, jejíž výsledek byl oříznut na 32 bitů; SPARC-V9
přidává další 4 bity (xcc – Extended Integer Condition Codes), kde nedochází k oříznutí
16
pokud ke skoku nedojde, následující instrukce se provede vždy; pokud ke skoku dojde, následující
instrukce se chová jako NOP (tj. vykonání dalších instrukcí je pozdrženo)
7
Analýza možností optimalizace binárního kódu
zvyšuje či snižuje dle skutečného provedení skoku. Pokud čítač obsahuje nižší hodnoty, je
predikováno, že ke skoku nedojde, pokud obsahuje vyšší hodnoty, je predikován opak.
K nastavení počáteční hodnoty se využívá hodnoty predikčního bitu v instrukci větvení.
UltraSPARC-IIi implementuje i 4-úrovňový RAS (viz procesor Alpha), k predikci volání
procedur.
Procesor IA-64 obsahuje u instrukcí větvení velkou podporu, jak řešit jejich (vzhledem
k optimalitě vykonávání kódu) problematické chování (instrukce větvení ruší tok souvislého
bloku instrukcí). Procesor IA-64 eliminuje používání instrukcí větvení pomocí 64 1-bitových
predikátových registrů (Predicate Register), pomocí nichž je možno mnoho běžných situací
(které nelze na jiných procesorech řešit jinak než instrukcí větvení) vyřešit bez použití instrukce
větvení. Drtivé většině instrukcí procesoru IA-64 lze přiřadit jeden predikční registr, který
v okamžiku spuštění instrukce určí, zda-li se instrukce opravdu vykoná či nikoli (instrukce se pak
chová jako NOP). Predikátový registr P0 je vždy nastaven a jeho hodnotu nelze změnit.
U instrukcí kontrolující platnost podmínky (CMP) vystupují vždy dva predikátové registry,
kdy jeden je nastaven a druhý vynulován dle platnosti podmínky. Následující příklad ukazuje
běžný případ použití tohoto mechanismu.
Kód v
if (a
c =
else
d =
jazyce C
> b)
c + 1;
d * e + f;
Pseudo-kód procesoru IA-64
pT, pF = compare (a > b)
if (pT) c = c + 1
if (pF) d = d * e
if (pF) d = d + f
;
;
;
;
nastav pred.reg. pT, pF
instrukce svázána s pT
instrukce svázána s pF
instrukce svázána s pF
příklad 1 – použití predikátových registrů na IA-64
Procesor IA-64 obsahuje instrukce nepodmíněného větvení17 a to jak s návratem, tak
bez návratu. Obě varianty mohou obsahovat buď přímý 20-bitový operand, který relativně
adresuje cílovou adresu, nebo explicitně definovaný registr větvení (Branch Registr) obsahující
absolutní cílovou adresu. V případě větvení s návratem instrukce obsahuje číslo registru větvení,
kam se uloží adresa následující instrukce. Procesor má 8 64-bitových registrů větvení a instrukce
pro výměnu hodnot mezi obecnými registry a registry větvení.
Predikce podmíněných nebo nepřímých instrukcí větvení nejsou v dokumentaci k IA-64
přesně popsány a jsou závislé na jeho implementaci. Překladač má ovšem možnost pomoci
predikci skoků (pokud ji procesor implementuje) specifikováním několika parametrů v kódování
instrukce větvení. Lze určit, zda-li se mají pro danou instrukci větvení alokovat prostředky
pro predikci (např. historie skoků apod.) a jakým způsobem se má instrukce predikovat, pokud
pro ni žádné prostředky dosud nejsou alokovány. Dále lze definovat, jak progresivně se má
načítat kód v místě cílové adresy.
Sada instrukcí větvení procesoru Motorola 68000 je podobná sadě u procesorů IA-16 a
IA-32, která je popsána v odstavcích „Instrukce“ kapitol B.2 a B.5.1 dodatku B. Predikce skoků
procesoru IA-32 s architekturou P5 (Intel Pentium, Intel Pentium MMX) a P6 (Intel Pentium Pro,
II a III) je popsána v odstavcích „Predikce skoků“ kapitol 4.3 a 4.4.
Instrukce větvení JVM lze rozdělit na ty, které větví pouze v rámci aktuálně vykonávané
metody a na ty, které volají přístupnou18 metodu třídy či rozhraní (ať už aktuální či jiné instance),
17
instrukci větvení lze také přiřadit predikátový registr a tudíž není třeba, aby procesor obsahoval
podmíněnou instrukci větvení
18
zjednodušeně je přístupná metoda buď libovolná metoda stejné třídy, či veřejná (Public) metoda jiné
třídy či chráněná (Protected) metoda potomka, podobně u přístupné třídy, přesněji viz [LIYE99] a
[GJSB00]
8
Analýza možností optimalizace binárního kódu
či provedou návrat z metody. Do první skupiny patří podmíněné (if_...) i nepodmíněné (goto)
instrukce větvení, nepodmíněné mohou být s návratem (jsr, ret) i bez návratu, dále sem patří
dvě instrukce (lookupswitch, tableswitch) prakticky ekvivalentní konstruktu switch z vyšších
jazyků. Instrukce z první skupiny mohou mít pouze přímý relativní ofset uložený bezprostředně
za operačním kódem instrukce.
Ve druhé skupině je řada instrukcí, které volají virtuální, nevirtuální, statické aj. metody
tříd a rozhraní (invoke...) a instrukce (...return) pro ukončení metody, návrat za instrukci
následující po volání metody a vrácení návratové hodnoty. Sem také patří instrukce (athrow)
vyhazující výjimku (instance třídy Throwable). Tyto instrukce pracují s operandem na zásobníku
operandů, jež je odkazem na instanci přístupné18 třídy. Při volání virtuální metody interpret JVM
rekurzivně vyhledá správnou volanou metodu (nemusí být volána metoda uvedené instance, ale
některého jejího potomka) a z bezpečnostních důvodů kontroluje, zda-li je volaná metoda
přístupná aktuální instanci třídy, tyto operace nejsou triviální a zpomalují volání virtuálních
metod.
Instrukce větvení s přímým operandem jsou lépe predikovány procesorem a jsou
bezesporu přijatelnější pro analýzu binárního kódu, která předchází jeho optimalizaci. Instrukce
s nepřímým operandem značně ztěžují algoritmy analýzy binárního kódu, v některých případech
lze však obejít i tato omezení (viz odstavce „Konstrukt switch“ a „Větvení na procesorech typu
RISC“ v kapitole 3.4.2 a odstavec „Algoritmus rozpoznání konstruktu switch“ kapitoly A.2
dodatku A). Podrobné znalosti predikčního mechanismu procesoru jsou nutné při zjišťování
přesného toku programu během optimalizace binárního kódu.
Shrnutí
Pokud by se mělo provést základní dělení výše uvedených procesorů, je nejlépe je dělit
podle obvyklého dělení na procesory typu RISC (Reduced Instruction Set Computer) a CISC
(Complex Instruction Set Computer). Alpha, SPARC-V9 a IA-64 pak spadají mezi procesory
RISC; IA-16, IA-32 i Motorola 680x0 jsou pak procesory typu CISC. Novější procesory IA-32
s architekturou P6 jsou v některé literatuře19 označovány jako post-RISC – ačkoli se navenek
tváří jako CISC, ve skutečnosti jsou vnitřně RISC-ové20.
JVM je natolik odlišný od ostatních procesorů, že ho nelze zařadit ani do jedné z obou
skupin. Odlišnost JVM je dána specifičností jeho použití, je určen pro vykonávání programů
na klientských stanicích, které jsou však uloženy na internetových serverech. Jeho hlavními
přednostmi jsou přenositelnost programů (nezávislost na cílové platformě), bezpečnost (JVM má
možnost zkontrolovat program při linkování jednotlivých tříd, těsně před jeho vykonáním) a malá
velikost kódu (z důvodu malých přenosových rychlostí na internetu), což jsou jiná kritéria než
u ostatních procesorů.
Ideálním procesorem k analýze a optimalizaci pro něj určeného spustitelného binárního
kódu je procesor, jenž obsahuje velké množství ortogonálních obecných registrů, jehož instrukce
jsou všechny stejně dlouhé a jsou zarovnány na určený násobek adresy, a který neobsahuje
instrukce větvení s nepřímým operandem. Z výše uvedených procesorů by se nejlepších výsledků
analýzy binárního kódu dosáhlo u JVM, zde však nemá smysl provádět optimalizaci binárních
souborů.
19
např. v [JIRO00]
instrukce architektury jsou za běhu překládány do mikroinstrukcí, které jsou pak vykonány procesorem,
podrobněji viz kapitolu 4.5
20
9
Analýza možností optimalizace binárního kódu
Více informací o procesorech Alpha, IA-32, SPARC-V9, UltraSPARC-IIi, Motorola
680x0, IA-64 a JVM lze nalézt v [COMP98], [INTL99a], [SPRC00], [SUN97], [CADA92],
[MOTO92], [INTL99b] a [LIYE99]. Specifikace jazyka Java je v [GJSB00]. Vzhledem k tomu,
že nástroj SCOUT je implementován pro rodinu procesorů IA-32, dodatek B jej popisuje
konkrétněji.
3.2.
Konstrukty vyšších jazyků
Konstrukt switch
Konstrukt switch větví kód do několika větví dle hodnoty rozskokové proměnné. Způsob
přeložení konstuktu switch závisí na konkrétním výčtu hodnot, které jsou v konstruktu obsaženy.
Pokud hodnoty jednotlivých větví tvoří souvislý blok či skoro souvislý21, je switch překládán
skoro vždy rozskokovou tabulkou a rozskoková proměnná, vhodně upravená, indexuje tuto
tabulku. Při úpravě proměnné se může například srazit rozsah hodnot tak, aby proměnná pořád
nabývala různých hodnot pro různé větve (pokud by nabývala jedné hodnoty pro více větví, je
nutno tuto proměnnou po použití rozskokové tabulky znova otestovat). Pokud hodnoty
rozskokové proměnné netvoří souvislý blok, je proměnná několikrát testována, zda náleží
půlenému intervalu.
Java Virtual Machine obsahuje ve svém instrukčním souboru 2 instrukce (lookupswitch
a tableswitch), které jsou ekvivalentní některým formám konstruktu switch z vyšších jazyků.
JVM neumožňuje nepřímá větvení – neobsahuje instrukci větvení, která by svůj operand měla
na zásobníku nebo v lokální proměnné.
Volání vnořené funkce či metody
V této kapitole je pod jménem funkce míněna i metoda, na této úrovni popisu, je jejich
zařazení stejné.
Je zajímavé sledovat, jakým způsobem si autoři specifikací jednotlivých procesorů
poradili s lokálními proměnnými a jejich uložením. Lokální proměnné jsou problematické tím, že
jejich alokace je pouze dočasná, ve chvíli, kdy je volána vnořená funkce, je třeba zajistit alokaci
lokálních proměnných této funkce a po skončení funkce uvolnit nepotřebný prostor. Na druhou
stranu jsou lokální proměnné ve funkci používány velmi frekventovaně, je vhodné umístit je
do rychle přístupných oblastí, např. registru.
Procesory IA-32 tento problém neřeší, a nechávají jej na překladači vyššího jazyka22. Ten
sice má možnost uložit proměnnou do registru, ale vzhledem k tomu, že IA-32 disponuje pouze 8
32-bitovými obecnými registry, děje se tak vždy jen krátkodobě a většinu proměnných ukládá
na zásobník. Registry používá k provádění aritmetických operací a v některých případech i
pro přenos parametrů knihovních funkcí.
IA-64 (i procesor UltraSPARC) se tento problém snaží minimalizovat používáním rámců,
které se mapují do speciálních registrů. Lokální proměnné jsou uloženy v těchto registrech.
Ve chvíli, kdy je volána vnořená funkce, je rámec posunut a tím jsou použity pro lokální
proměnné jiné registry. Pokud je úrovní zanoření příliš mnoho, jsou hodnoty registrů z vyšších
úrovní (a tedy nejdéle nepoužívané) překopírovány do speciálně k tomu určené paměti (Backing
21
pouze několik málo hodnot není uvedeno, přičemž výraz málo závisí na dalších okolnostech
respektive, řešil jej jejich předchůdce, který zavedl instrukce ENTER a LEAVE pro vytvoření a zrušení
rámce na ukládání vstupních parametrů a lokálních proměnných na zásobníku, tyto instrukce lze však
nahradit posloupností jiných instrukcí, jejichž vykonání je rychlejší
22
10
Analýza možností optimalizace binárního kódu
Store) a uvolněné registry jsou použity pro další rámec. Při vynořování z funkce se tato činnost
opakuje v obráceném pořadí. Speciální registry jsou vždy adresovány relativně k aktuálnímu
rámci, kód si neuvědomuje skutečné umístění těchto registrů. IA-64 navíc umožňuje specifikovat
velikost okna (nebo-li počet registrů), které mají sdílet volající i volaná funkce.
U JVM se při volání metody alokuje pro ni vlastní rámec, ten však není nikdy mapován
do registrů, je použita paměť z hromady. Rámec obsahuje prostor pro zásobník operandů a
lokální proměnné.
3.3.
Formát vstupního souboru
Možnosti optimalizací jsou omezeny nejen typem procesoru, ale i formátem vstupního
souboru. V průběhu vytváření spustitelného binárního kódu prochází kód několika stavy. Zcela
na počátku se nachází zdrojový text (Source File) programu. Přeložením zdrojového souboru
překladačem (Compiler)23 vzniká cílový soubor (Object File)24, který sice již obsahuje binární
data, ale ještě není spustitelný. Teprve po spojení (Linking) cílového souboru (či několika
souborů) se vytváří spustitelný soubor (Executable File). (Tyto fáze se dále komplikují
příchodem nových jazyků, např. Java, u kterých binární kód představuje jakousi mezivrstvu,
nativní kód je vytvářen až za běhu programu).
Obecně je možno kód optimalizovat v kterékoli jeho fázi – běžně bývá optimalizován
překladačem v první fázi, kdy se počítají použité konstanty, vytýkají se výrazy před cykly apod.
V této chvíli však ještě nejde o optimalizaci binárního kódu. Binární data obsahuje až cílový a
spustitelný soubor.
Čím syrovější kód (blíže zdrojovému kódu), tím větší jsou možnosti jeho optimalizace.
Toto tvrzení je dáno tím, že před vlastní optimalizací binárního kódu je třeba provést rozsáhlou
analýzu tohoto kódu. Výstupem analýzy je informace nejen o tom, jak je program členěn
do funkcí a programových bloků, kudy probíhá tok programu a tok dat, ale i například tak
základní informace jako je popis oblastí, kde kód leží a co všechno v souboru představuje kód a
co data (počítače s von Neumannovou architekturou nerozlišují při zaznamenávání do souboru
mezi kódem a daty). Toto je stoprocentně rozpoznatelné až z kontextu spuštěného programu, ale
ne při statické analýze. Takováto analýza nemusí být vůbec triviální záležitostí.
Formátem binárního souboru se rozumí výčet hlaviček v souboru a popis položek, které
hlavička obsahuje, včetně přesného umístění a způsobu kódování položek. Prakticky každá
platforma používá svůj vlastní formát spustitelného souboru a i na jediné platformě může
existovat několik různých formátů. Pro formáty cílových souborů prakticky neexistuje standard a
dokonce i jednotlivé překladače stejného jazyka na jedné platformě mohou generovat různé
cílové soubory, každý v jiném formátu.
Popis spustitelných a cílových souborů pro platformu Microsoft Windows 95/98/NT, tzv.
PEFF formát (Portable Executable File Format ), je v [MSFT99], [TISC] a v [PIET94]. Zajímavé
je, že tento formát vychází ze zastaralého, dnes již nepoužívaného, formátu souborů na
unixových systémech, tzv. COFF formátu (Common Object File Format). Formáty COFF a
novější ELF (Executable and Linkable Format ) jsou popsány v [DJGP96] a [TISC95a]. Popis
OMF (Object Module Format) formátu používaného operačními systémy MS-DOS a OS/2 lze
nalézt v [TISC95].
23
24
i tento krok může u některých jazyků obsahovat několik fází, např. pre-procesor u jazyka C/C++
též se používá termín linkovatelný či spojovatelný soubor
11
Analýza možností optimalizace binárního kódu
Každý z uvedených formátů obsahuje několik hlaviček s pevně danou velikostí, které
obsahují odkazy na vlastní data či kód. Data jsou vnitřně dělena na souvislé úseky, zde zvané
sekce. Sekce je pak mapována na konkrétní virtuální adresy s tím, že danou aplikaci operační
systém zavede na požadované místo.
V některých případech nemůže OS zavést aplikaci na požadované umístění a použije jiné
adresy, takovýto binární kód musí obsahovat sekci s relokační informací25. Relokační informace
je seznam adres v souboru s binárním kódem, které obsahují absolutní odkaz, tyto odkazy se
musí přepočítat (každému odkazu se přičte rozdíl mezi původní požadovanou adresou a
skutečnou mapovanou adresou).
Určité sekce mohou mít specifickou funkci, např. obsahují ladící informace, seznam
symbolických jmen apod. Určité sekce jsou pouze ve spustitelných souborech, jiné pouze
v cílových, další mohou být v obou.
Pokud je soubor spustitelným programem, obsahuje hlavička souboru adresu startovního
bodu. Pokud je soubor knihovnou funkcí, obsahuje soubor sekci se seznamem exportovaných
funkcí, každá položka seznamu obsahuje jméno funkce a adresu, kde daná funkce začíná.
Pokud aplikace obsahuje volání importované funkce, je tento odkaz veden do speciální
sekce. Operační systém při spouštění takového programu nahradí všechny výskyty odkazů do
této sekce skutečnou virtuální adresou, na kterou je volaná funkce mapována.
Každá sekce má svoje atributy, které dále definují vlastnosti dat v sekci. Například sekce
se spustitelným kódem má atribut spustitelného kódu, sekce s inicializovanými daty má atribut
povolující zápis do této sekce, sekce s neinicializovanými daty má atribut, že datová část této
sekce je prázdná, sekce s relokační informací či ladícími informacemi má atribut, pro
nemapování této sekce do paměti atd.
Adresy, na něž se mapují sekce, jsou vždy dělitelné velikostí stránky paměti. Sekce jsou
uloženy v souboru na pozicích, které jsou dělitelné velikostí diskového sektoru, těchto dvou
vlastností využívá operační systém pro rychlé zavedení programu do paměti a ochraně některých
sekcí před nežádoucím zápisem.
Prakticky jedinými povinnými částmi binárního souboru, u kterých je používán standardní
(mezi různými překladači stejný) způsob zakódování, jsou sekce obsahující kód a data, jež jsou
součástí programu a které jsou mapovány na virtuální adresy. Na přítomnost jiných informací
(symbolické názvy, ladící informace apod.) se nelze spolehnout, nemusí být buď v souboru vůbec
uloženy, případně mohou být v nestandardním formátu. Přítomnost relokační informace může
také pomoci při analýze binárního souboru.
3.4.
Analýza binárního kódu
Jak už bylo řečeno, analýza binárního kódu má za úkol podat informaci o binárním kódu.
Tato informace se získá jednak z hlaviček analyzovaného souboru, jednak průchodem kódu.
Analýza musí být schopna určit, kde v souboru se binární kód nachází. Dále je třeba, aby
rozpoznala všechna větvení v programu. Optimalizovaný kód totiž nemusí mít stejnou velikost,
jako kód původní, cílové adresy větvení se mohou lišit.
25
o nutnosti relokační informace hovoří i oficiální literatura k formátu PEFF pro OS Windows 95/98,
při testování příkladů byly však objeveny i případy, kdy sice kód neobsahoval relokační informaci či
obsahoval poškozenou či neúplnou relokační informaci a přesto byl na OS Windows 95/98 bez problémů
spustitelný
12
Analýza možností optimalizace binárního kódu
Existují dva základní přístupy, jak provádět analýzu kódu, zde se označují termíny
statická analýza kódu a dynamická analýza kódu.
V programu SCOUT je použita statická analýza kódu, která nepotřebuje analyzovaný
program spouštět, ani nevyžaduje žádnou speciální asistenci obsluhy.
Zde je třeba zdůraznit jednu skutečnost. Veškeré algoritmy (analyzující i optimalizující)
zde popisované mají především zajistit vyšší rychlost spuštěného kódu, na velikost kódu se hledí
až v druhé řadě a nevadí tedy jeho zvětšení (i když jsou popisovány i algoritmy na zmenšení
velikosti kódu). Hlavní prioritou algoritmů (před zrychlením a zmenšením programu) je
samozřejmě zachování původní funkčnosti programu.
3.4.1. Zavedení některých pojmů
Zde jsou zavedeny některé pojmy, se kterými se lze setkat v dalším textu. Většinou jde
o obecně známá označení, formálnější a obsažnější definice lze nalézt v [JIRO00].
Instrukce
Řetězec symbolů, které při interpretaci procesorem způsobí jednoznačnou a definovanou
změnu stavu stroje. Každá instrukce se potom skládá ze dvou částí – operačního kódu, který
specifikuje, jaká změna stavu bude docílena provedením této instrukce a specifikace
identifikátorů v adresovém prostoru počítače, které mají být při změně stavu použity jako
parametry.
Operand instrukce
Parametr, se kterým instrukce pracuje. Operand může být zadán explicitně či implicitně,
instrukce jej může číst, zapisovat či modifikovat (číst i zapisovat). Operandem může být obecně
libovolný registr (či jen jeho část), adresa paměti či brána vstupně/výstupního zařízení. Jedna
instrukce může mít několik operandů.
Varianta (modalita) instrukce
Množina možných operandů je široká, a proto nebývá obecně v libovolné instrukci možno
použít všechny dostupné operandy, je specifikováno, které povolené operandy to jsou. Typicky to
bývá výčet některých registrů, paměťový operand apod. Není výjimkou, že možným operandem
může být pouze konkrétní registr a žádný jiný. Varianta instrukce pak označuje danou instrukci
spojenou s použitím konkrétního operandu.
Kódování varianty instrukce
Operační kód instrukce s kódováním konkrétních použitých operandů.
Instrukce větvení
Instrukce procesoru, která změní sekvenci vykonávaných po sobě jdoucích instrukcí.
Operandem (implicitním či explicitním) instrukce větvení bývá cílová adresa, která určuje
následující vykonávanou instrukci. Instrukce větvení lze rozdělit do několika skupin podle toho,
zda-li jde o nepodmíněnou či podmíněnou instrukci (změna adresy závisí na splnění určité
podmínky) a zda-li jde o instrukci větvení bez návratu či s návratem (procesor si poznamená
adresu instrukce následující za instrukcí větvení, instrukční sada procesoru obsahuje jinou
instrukci větvení, která návrat obstará).
13
Analýza možností optimalizace binárního kódu
Svazek instrukcí
Souvislý úsek kódu se nazývá svazek instrukcí, pokud v celém programu neexistuje
instrukce větvení, jejímž cílem by byla libovolná instrukce ve svazku, kromě první instrukce.
Pokud nebude uvedeno jinak, hovoří se v dalším textu o svazku v maximální velikosti.
3.4.2. Statická analýza binárního kódu
Algoritmus statické analýzy
Statická analýza prozkoumá binární kód bez nutnosti jeho spuštění.
Základní algoritmus statické analýzy, pro zjištění oblastí se spustitelným kódem a
pro zjištění všech cílových adres instrukcí větvení, je jednoduchý. V průběhu algoritmu se
používá seznam adres, do něhož se zařazují cílové adresy větvení. Na počátku seznam obsahuje
jediný prvek – adresu vstupního bodu programu, případně několik prvků – adresy všech
exportovaných funkcí. Algoritmus vždy vybere jednu adresu ze seznamu a od ní počne
analyzovat všechny následující instrukce a to až do okamžiku, kdy narazí na nepodmíněnou
instrukci větvení, která nenávratně mění tok programu. V té chvíli neanalyzuje následující
instrukci, ale vybere další prvek ze seznamu. Pokud narazí na podmíněnou instrukci větvení,
přidá cílovou adresu větvení do seznamu a pokračuje na instrukci následující za instrukcí větvení.
V průběhu algoritmu vzniká druhý seznam – seznam svazků instrukcí, který obsahuje popisy
kusů analyzovaného kódu. Pokud adresa, která se vybere ze seznamu adres, ukazuje do vnitřku
(neukazuje na první instrukci svazku) již existujícího svazku, rozdělí se tento na dva svazky.
Oblast se spustitelným kódem je sjednocením všech svazků, cílové adresy všech větvení jsou
počátky všech svazků.
Formální zápis algoritmu je následující.
1) Inicializuj seznam adres La tak, že obsahuje adresy všech
exportovaných funkcí a/nebo adresu vstupního bodu programu
2) Inicializuj seznam svazků Lb, který je prázdný
3) Dokud je seznam La neprázdný opakuj body 4 až 9
4) Vezmi adresu ze seznamu La, ukazateli p přiřaď hodnotu adresy
5) Pokud p ukazuje do některého svazku již analyzovaných instrukcí, tak
a) pokud p neukazuje na první instrukci ve svazku, rozděl svazek
v adrese p na dva a přidej vzniklý svazek do seznamu Lb
b) skonči (běž na bod 3)
6) Analyzuj instrukci I ležící na adrese p
7) Pokud je instrukce I nepodmíněnou instrukcí větvení bez návratu, jež
mění nenávratně tok programu (RET, JMP), tak
a) ulož rozpracovaný svazek do seznamu Lb
b) skonči (běž na bod 3)
8) Pokud je I nepodmíněnou instrukcí větvení s návratem, jež mění tok
programu pouze dočasně (CALL), tak
a) přidej cílovou adresu větvení do seznamu La
b) ukazateli p přiřaď adresu následující za instrukcí I
c) běž na bod 5
9) Pokud je I podmíněnou instrukcí větvení (Jcc, LOOP), tak
a) proveď vše úplně stejně jako v případě bodu 8
alg. 1 – statická analýza binárního kódu
Pozn. 1: Algoritmus (alg. 1) nepopisuje patologické stavy, kdy dojde k chybě na základě
některé následující události. Cílová adresa větvení ukazuje mimo program, případně mimo sekci
obsahující spustitelný kód. Cílová adresa ukazuje doprostřed již analyzované instrukce.
Analyzátor narazí na neznámou nebo chybně kódovanou instrukci.
14
Analýza možností optimalizace binárního kódu
Pozn. 2: Algoritmus reaguje pouze na běžné instrukce větvení, například nerozeznává
podmíněnou instrukci větvení, jež mění tok programu pouze dočasně (tzv. s návratem). Pokud by
taková nebo jiná nerozeznávaná instrukce existovala26, není těžké algoritmus pozměnit.
Pozn. 3: Mnemotechnický název instrukce v závorce je převzat z procesoru IA-32.
Slabiny algoritmu
Popsaný algoritmus statické analýzy není sám o sobě dostačující. Jeho slabina je
v neznalosti kontextu procesoru při zpracování instrukce používající nepřímé operandy.
Prakticky většina operandů jsou nepřímé operandy, ovšem při znalostech vlastností
konkrétního typu procesoru a použitého překladače a na základě statického kontextu (předchozí a
následující instrukce) instrukce lze zjistit, jaký je pravděpodobný význam operandu. Toto zjištění
v mnoha případech postačuje, není třeba znát konkrétní hodnotu.
V praxi překladač překládá určité sémantické prvky zdrojového souboru uniformním
způsobem. Např. příkaz switch, či dereference ukazatele na funkci, nutí překladač generovat
specifický kód, který je při zpětné analýze binárního kódu rozpoznatelný. Takovýto specifický
kód je zde označován pojmem idiom.
Následují nejběžněji se vyskytující příklady a způsoby jejich řešení.
Konstrukt switch
Uvedený alg. 1 nepopisuje situaci, kdy dojde k analýze nepřímé instrukce větvení
(hodnota cílové adresy závisí na kontextu spuštěného programu). Statická analýza nemůže
obecně zjistit, na kterou adresu (nebo adresy) ukazuje adresový operand instrukce. Instrukce
větvení s nepřímým operandem se v binárním kódu může objevit například v případě konstruktu
switch jazyka C.
Pokud jsou jednotlivé hodnoty rozskokové proměnné dostatečně blízko u sebe, je
pro program výhodnější použít rozskokovou tabulku namísto jiného způsobu (porovnávání
hodnot, apod.), následující příklad ilustruje tuto situaci.
Zdrojový kód:
int func (int a) {
int b;
switch (a) {
case 2 :
b = 3; break;
case 3 :
b = 5; break;
case 4 :
b = 7; break;
case 5 :
b = 11; break;
}
return b;
}
Přeložený kód:
1.
PUSH EBP
2.
MOV
EBP,ESP
3.
SUB
ESP,00000008
4.
MOV
EAX,[DWORD SS:EBP+08]
5.
MOV
[DWORD SS:EBP-08],EAX
6.
MOV
ECX,[DWORD SS:EBP-08]
7.
SUB
ECX,00000002
8.
MOV
[DWORD SS:EBP-08],ECX
9.
CMP
[DWORD SS:EBP-08],00000003
10.
JA
00401047
11.
MOV
EDX,[DWORD SS:EBP-08]
12.
JMP
[DWORD DS:EDX*4+0040104E]
...
příklad 2 – konstrukt switch
Z vlevo uvedeného zdrojového kódu v jazyce C vygeneroval překladač binární kód.
V instrukci číslo 12 se objevuje nepřímý operand. Instrukce číslo 7, 9, 10 a 12 lze považovat
za idiom, který vyznačuje použití konstruktu switch27. Druhý operand v instrukci číslo 7 označuje
26
podmíněná instrukce s návratem existuje např. u procesoru IA-64
neznamená to ovšem, že bude konstrukt switch vždy přeložen uvedeným způsobem, je to pouze jedna
z možných variant
27
15
Analýza možností optimalizace binárního kódu
nejmenší hodnotu rozskokové proměnné, druhý operand v deváté instrukci je rozsah možných
hodnot rozskokové proměnné, konstantní posunutí (Displacement) v poslední instrukci je
adresou, kde jsou uvedeny 4-bytové adresy jednotlivých větví konstruktu switch.
Více o rozpoznávání switch konstruktů je možno dohledat v technické zprávě [CIEM98].
Větvení na procesorech typu RISC
V závislosti na typu procesoru, pro který je binární soubor přeložen, se mohou objevovat
častěji nebo méně často určité instrukce, které problematizují vstupní analýzu.
Např. na procesorech typu RISC jsou obvyklé instrukce (tedy i instrukce větvení), které
nemají přímé operandy. Prakticky pouze několik málo instrukcí je vyhrazeno pro zápis do paměti
a čtení z paměti a pro zápis konstanty do registru. Pro procesory typu CISC je naopak obvyklé, že
mají instrukce pracující s přímými operandy. V praxi tento rozdíl vede k tomu, že na RISC-ových
procesorech nemá většina instrukcí větvení přímý operand. Následující příklad ukazuje typické
vyplňování registrů před instrukcí větvení na procesoru SPARC.
...
1.
2.
3.
4.
SETHI %HI(0x10800), %L1
ADD %L1, 0x258, %L1
JMP %L1
NOP
;
;
;
;
nastav adresu skoku (0x10a58)
do registru %L1
instrukce větvení
prázdná instrukce
příklad 3 – větvení na procesoru SPARC
Instrukce číslo 1, 2 a 3 tvoří idiom, který označuje skok na konstantní adresu a může být
bezpečně rozpoznán analyzátorem, ačkoli je v instrukci větvení použit nepřímý operand.
3.4.3. Problematika statické analýzy binárního kódu
Výše je uvedeno několik možných problémů, které mohou vzniknout při statické analýze
binárního kódu a způsoby jejich řešení. Ovšem ani tyto příklady nevyčerpávají všechny
potencionální „nepříjemnosti“, jež mohou nastat. Statická analýza se s jistými případy nemusí
umět vyrovnat. Pokud nerozpozná všechny cílové adresy instrukcí větvení v programu, či
neoznačí všechny oblasti se spustitelným kódem, dojde k nedokonalému rozboru binárního
souboru. Tento stav se zde označuje termínem částečná analýza. Analýza binárního kódu, která
rozpozná všechny cílové adresy všech instrukcí větvení v programu a označí správně všechny
oblasti se spustitelným kódem, se nazývá úplnou analýzou binárního kódu.
Na první pohled by se mohlo zdát, že nemá smysl optimalizovat kód, pokud nedojde
k jeho úplné analýze, protože až na základě výsledků analýzy je možno kód optimalizovat.
Při použití optimalizace, která nezachovává velikost kódu, dojde k posunutí svazků instrukcí,
cílové adresy nerozpoznaných instrukcí větvení zůstanou neopraveny a tudíž při spuštění
takových instrukcí dojde ke změně toku programu na nesprávnou adresu – program se nebude
chovat korektně.
Toto zdání je však klamné. Jistě, nerozpoznaný kód nelze optimalizovat28, takový kód
zůstane ponechán nezměněný. Ovšem nerozpoznání všech instrukcí větvení29 nemusí vždy vést
ke zhroucení optimalizovaného programu. Při splnění určitých předpokladů a při generování
optimalizovaného kódu speciálním způsobem, nemusí být optimalizace nedokonale
analyzovaného kódu závadná.
28
29
i když existují další techniky, které napomáhají označit oblasti se spustitelným kódem, viz dále
a tudíž neopravení jejich cílových adres
16
Analýza možností optimalizace binárního kódu
Dále jsou uvedeny některé příklady situací, které algoritmus statické analýzy nevyřeší
korektně.
Sebemodifikující kód
Výskyt programů, které modifikují sami sebe, není příliš častý, přesto se občas takový
vyskytne. Typickým příkladem sebemodifikujícího kódu je virus, který se snaží zamaskovat svoji
činnost30. K tomuto účelu lze použít různé techniky, např. schování vlastního kódu viru v datové
části souboru a vytvoření odkazu na tento kód až při spuštění programu, viz následující příklad.
Případný antivir nemůže takto prohlédnout infikovaný kód (protože jej za kód nepokládá).
CALL
DB
conta
DD
procX:
MOV
RET
virusOffset:
...
procX
; volání procedury
0E9h
; kód instrukce JMP
0
; vyhrazené místo
[BYTE DS:conta],virusOffset-procX
; kód viru
příklad 4 – maskující se virus
Existují i další důvody pro použití modifikace kódu. Následující příklad ukazuje aplikaci
jednoduchého algoritmu při šifrování kódu.
encrypt_decrypt:
MOV
xor_loop:
MOV
XOR
MOV
INC
CMP
JLE
RET
EBX, offset virus_code ; začátek šifrovaného kódu
AH, [EBX]
; přečti byte
AH, encrypt_val
; šifruj/dešifruj byte
[EBX], AH
; zapiš byte
EBX
EBX, offset virus_code+virus_size
xor_loop
; cykli
příklad 5 – šifrování kódu
Předpokladem pro funkčnost výše uvedených příkladů je povolený zápis do kódové
sekce. Potřebné povolení však není problém si opatřit pomocí volání služeb OS.
Je zřejmé, že statická analýza nemůže správně rozpoznat záměr obou programů. V prvním
případě nerozpozná existenci těla viru. V druhém případě nezanalyzuje zašifrovaný kód.
Problém podmíněného skoku
Algoritmus analyzátoru při nalezení podmíněné instrukce větvení v dobré víře
předpokládá, že běh programu může pokračovat oběma směry – při splnění i při nesplnění
podmínky. Algoritmus nemůže tušit, zda-li a kdy se větvení provede a proto uvažuje obě větve.
V následujícím příkladu se však rozhodne špatně a dojde k analýze dat31, jejich následné
optimalizaci a nenávratnému poškození.
variable_a
DD
...
MOV
TEST
0
; proměnná, jejíž hodnota se nemění
EAX, variable_a
EAX, EAX
JZ
@always
; skok se provede vždy
; zde jsou data, jež analyzátor považuje za kód
příklad 6 – chybná interpretace podmíněného skoku
30
31
moderní antivirové nástroje disasemblují prohlížený program a hlásí výskyty podezřelých kusů kódu
jež jsou nesprávně považována za kód
17
Analýza možností optimalizace binárního kódu
Relativní adresování paměti
Běžné procesory nemají instrukci, která by adresovala paměť relativně od adresy této
instrukce. Tato adresa se však dá snadno zjistit, následující příklad ukazuje jak.
proc:
CALL
MOV
...
MOV
RET
proc
[DWORD DS:EAX+rOff], value
; adresuj relativně
EAX, [DWORD SS:ESP]
; načti návratovou adresu
příklad 7 – relativní adresování paměti
Při použití předchozího příkladu hrozí, že pokud se během optimalizace takového kódu
zvětší relativní vzdálenost mezi instrukcí adresace paměti a vlastní adresovanou proměnnou,
dojde k adresaci jiného nesprávného paměťového místa.
Vícenásobné použití jedné oblasti
V následujícím příkladu jsou v levém sloupci napsány konkrétní adresy, na kterých je
umístěna daná instrukce, vpravo pak binární hodnoty, kterými je instrukce kódována. Příklad
ukazuje, že je možno jeden kus kódu (na adrese 004020DA) použít i k jinému účelu, např.
k naplnění registru EBX konstantou 0x01010101.
004020DA
004020DC
0041D0B2
ADD
[DWORD DS:ECX], EAX
ADD
[DWORD DS:ECX], EAX
. . .
MOV
EBX, [DWORD DS:004020DA]
01 01
01 01
8B 1D 004020DA
příklad 8 – použití kódu v datovém operandu
Optimalizátor může instrukce na adresách 0x004020DA a 0x004020DC nahradit jinými, či
může zpřeházet pořadí jejich vykonávání. Instrukce na adrese 0x0041D0B2 pak nenačte
do registru EBX správnou konstantu.
3.4.4. Předpoklady korektní analýzy i optimalizace binárního kódu
Takovýchto či podobných příkladů, jako je několik výše uvedených, je nespočetně. Každý
z nich přivádí algoritmus analýzy na nesprávnou cestu, jež vede ke špatnému pochopení běhu
kódu, k nekompletnímu či, ještě hůře, ke špatnému označení oblastí se spustitelným kódem.
Následný algoritmus optimalizace pak nutně musí produkovat chybné výsledky, což vede
v lepším případě k nesprávnému chování optimalizovaného programu, v horším případě
ke zhroucení programu32.
Z výše uvedených příkladů je patrné, že existuje několik nebezpečí – neoznačení všech
oblastí s kódem, označení nesprávné oblasti s kódem, existence oblastí, jež mají násobnou funkci
(vystupují v roli kódu i dat či vícekrát různých dat), existence oblastí s kódem, jež jsou
modifikovány za běhu programu.
Nejde si nevšimnout jedné skutečnosti, všechny výše uvedené příklady, jež mohou zmást
algoritmus analýzy, vychází z nestandardní, ne zcela běžné situace. Všechny příklady (až
na příklad viru) byly uměle vykonstruovány a využívají slabiny počítače s von Neumannovou
architekturou, kdy kód i data jsou stejně kódovány. Binární kód, který vznikne přeložením
z některého vyššího jazyka, prakticky nikdy neobsahuje podobně vykonstruované příklady,
nebezpečí samozřejmě zůstává u programů psaných v asembleru, a i zde zůstává otázka, zda-li
jsou takové techniky masově používány, ojediněle se určitě vyskytnou.
32
v určitých případech se může zhroutit i samotný operační systém
18
Analýza možností optimalizace binárního kódu
Předpokladem pro správnou analýzu binárního kódu je, že analyzovaný soubor
neobsahuje žádné nestandardní programové způsoby podobné výše uvedeným příkladům. Je
nutno dodat, že uvedené předpoklady příliš nezmenší množinu optimalizovatelných programů a
proto nemají velký vliv na použitelnost nástroje, jež využívá tohoto přístupu. Tento předpoklad
však nevyloučí problém částečné analýzy kódu, např. při použití ukazatele na funkci. Následující
technika pomůže tento problém zčásti eliminovat.
Duplikace kódu
Problém částečné analýzy lze vyřešit poměrně elegantní technikou, zde nazvanou
duplikace kódu. Duplikace kódu zajistí, že neprovedení změn na cílových adresách
u nerozpoznaných instrukcí větvení nebude mít vliv na běh kódu. Kód programu je do cílového
souboru zapsán ve dvou variantách. V první variantě je uveden celý původní, až na cílové adresy
některých instrukcí větvení nezměněný, kód. Druhá varianta obsahuje optimalizovaný kód, který
byl označen vstupní analýzou. Původní kód by měl ležet na původních adresách paměti,
optimalizovaný kód by měl ležet na jiných vhodných adresách. Výraz „vhodné adresy“ souvisí
s použitou platformou a je vysvětlen později. Popsaný přístup sice zvětší velikost binárního
souboru, to však není v rozporu s předpoklady.
Následující obrázek ilustruje situaci.
Původní kód
Výstupní kód
1
1
2
2
3
3
Rozpoznaný kód
Nerozpoznaný kód
Optimalizovaný kód
Data
1
2
3
obrázek 1 – duplikace kódu
Tento přístup vyřeší i problém podmíněného skoku (ačkoli zřejmě zbytečně zahrne
do optimalizované části i kus špatně rozpoznaných dat) a problém vícenásobného použití jedné
oblasti (při zachování hodnoty ukazatele).
19
Analýza možností optimalizace binárního kódu
Pozn. Duplikace kódu je však dvousečná zbraň, vyřeší sice výše uvedené problémy,
naopak však velmi prodlužuje velikost kódu a může tím zhoršit jeho efektivnost33, jak je popsáno
dále.
Vhodná adresa pro umístění optimalizovaného kódu do binárního souboru
Jak již bylo uvedeno dříve, soubor obsahující binární kód se skládá ze sekcí. Sekce
obsahují souvislé kusy dat nebo kódu a bývají mapovány na konkrétní virtuální adresy.
Konkrétní mapované adresy jsou skoro neomezené34, avšak bývá zvykem používat stejné adresy.
Například v operačním systému Windows 95/98/NT/2000 se aplikace obvykle spojují na
počáteční adresu 0x40000035, přičemž prvních 0x1000 adres zůstává nevyužito. Jako první sekce
je řazena kódová sekce, následují přepisovatelná inicializovaná data a přepisovatelná
neinicializovaná data. Pořadí dalších sekcí (ladící informace, relokační informace, informace o
importovaných či exportovaných funkcích atd.) je většinou závislé na použitém překladači.
Každá sekce začíná na nejbližší další volné adrese dělitelné velikostí paměťové stránky. Formát
PEFF umožňuje zakódovat i několik kódových sekcí do jednoho souboru. Vhodnou adresou pro
umístění sekce s optimalizovaným kódem na platformě Windows 95/98/NT/2000 tedy může být
sekce ležící za poslední původní sekcí.
Platforma Digital Unix / Alpha využívá podobný přístup s malými odchylkami. Kódová
sekce bývá spojována na adresu 0x12000000, zatímco následující datová sekce se obvykle
spojuje na adresu 0x14000000, obvykle vzniká dostatečně velký nevyužitý prostor, který může
být vhodný pro umístění optimalizovaného kódu.
3.4.5. Dynamická analýza binárního kódu
Zjištění vstupních informací optimalizace o programu jeho spuštěním se nazývá
dynamická analýza binárního kódu. Dynamická analýza má oproti statické variantě mnoho
výhod, potíže jí nečiní většina výše uvedených problémů (viz kapitoly 3.4.2 a 3.4.3) a dosahuje
vyšších úspěchů při rozhodování o cílové adrese nepřímých skoků. Nemalou výhodou je snadná
možnost si zjistit nejfrekventovaněji volané části kódu, kterým může následný optimalizátor
věnovat větší pozornost.
Na druhou stranu přináší její implementace mnoho problémů. Musí zcela přesně emulovat
chování nejen procesoru, kterému je binární kód určen, ale i celého systému – volání API
(Application Programming Interface) funkcí apod. Vyžaduje přítomnost obsluhy u programů,
které reagují na vstupy uživatele a neumožňuje provádět analýzu u cílových (nespustitelných)
souborů.
Ačkoli dynamická analýza přináší jistě lepší výsledky při určování, co v binárním kódu
představuje spustitelný kód a co data, nemusí tak rozhodnout ve všech případech. Některé části
kódu (reakce na chybové stavy, reakce na vnější události apod.) mohou být spouštěny jen
ve velmi řídkých případech a zůstává zde otevřena otázka, jak analyzátor určí, že analýza
binárního kódu je kompletní (tj. že již analyzoval všechen spustitelný kód).
33
viz tvrzení, které říká, že kratší kód se lépe vejde do kódové vyrovnávací paměti a tím je obecně rychleji
proveditelný než delší kód
34
až na prostor, kam se běžně mapují funkce jádra OS či jiná systémová data apod.
35
to, že jsou všechny programy spojovány na jednu (virtuální!) adresu, nijak nezabraňuje jejich pseudoparalelnímu spouštění na jednom počítači v jeden okamžik; je nutno si uvědomit, že operační systém
vytváří pro každý proces vlastní fyzický paměťový prostor, jednotlivé procesy tedy vzájemně nekolidují
20
Analýza možností optimalizace binárního kódu
Přítomnost sebemodifikujícího kódu sice může detekovat, problém však je, co s takovým
kódem při jeho následné optimalizaci. Modifikovanou část nelze přemístit a podmínka zachování
velikosti kódu (a tedy i jednotlivých adres) je přeci jen příliš svazující. Problém vícenásobného
využití jedné části kódu dynamická analýza také nevyřeší.
Následující příklad ukazuje, že nestačí pouze emulovat chování každé instrukce – načíst
správně její vstupní operandy a zapsat správně její výstupní, ale i správně emulovat celou
architekturu procesoru – například, pokud je provedení instrukce děleno do více fází.
MOV
MOV
DB
jmpOff
DD
codeNotExecuted:
...
codeExecuted:
...
EAX, 0x90909090
; 90 == NOP
[jmpOff], EAX
0xEB
; JMP codeExecuted
codeExecuted-codeNotExecuted
příklad 9 – přepis předzpracované instrukce
Při spuštění toho příkladu na procesoru, který používá několik fází k provedení jedné
instrukce (nejprve ji načte, pak dekóduje, pak vykoná) a který nerozpoznává, že dochází
ke změně instrukce, jež je již načtena v lokální paměti (Prefetch Queue)36, se provede větev
codeExecuted. Pokud si toho nebude vědom analyzátor, dojde ke spuštění větve
codeNotExecuted.
Situace se komplikuje v případě, kdy je používána vyrovnávací paměť. Některé procesory
(Intel 80486, Intel Pentium) pamatují na situaci, kdy dojde ke změně instrukce načtené
ve vyrovnávací paměti a označí tuto část vyrovnávací paměti jako neplatnou (Cache Invalidated),
jiné procesory (IA-64) však s touto situací nepočítají.
Pozn. Pro úplnost je třeba dodat, že moderní OS zabraňují aplikačním programům
zapisovat do kódové sekce, toto opatření lze však obejít zavoláním příslušné systémové funkce.
Shrnutí
Řešení výše uvedené problematiky je velmi komplikované. Pro opravdu korektní chování
dynamického analyzátoru je třeba počítat se všemi vlastnostmi architektury procesoru, navíc se
nelze spoléhat na kompatibilitu takto napsaného analyzátoru s budoucími procesory (z důvodu
možné změny v chování procesoru).
3.5.
Optimalizace kódu
Po rozboru vstupního programu je možno program optimalizovat. Vstupem algoritmu
optimalizace je binární soubor a informace, která vznikla během jeho analýzy. Optimalizace
binárního kódu je algoritmus, který pozmění binární kód takovým způsobem, že při zachování
jeho funkčnosti dojde ke zrychlení běhu kódu a/nebo zmenšení jeho velikosti. Možností
optimalizace je několik.
36
dokumentace k procesorům IA-32 jsou v této otázce dost zmatené, některé tuto problematiku mlčenlivě
přecházejí, jiné si vzájemně odporují; pravděpodobně se však jednotlivé typy architektur (Intel 80486, P5,
P6) chovají odlišně (nejsou tak zpětně kompatibilní!!), někdy je třeba změnu předzpracované instrukce
synchronizovat instrukcí větvení, jindy to nutné není
21
Analýza možností optimalizace binárního kódu
Odstraňování zbytečných instrukcí
Procesory typu RISC obvykle nemívají aritmetické instrukce, kde by jako operand
vystupovala paměťová proměnná, aritmetické instrukce zde pracují pouze s registry a pouze dvě
instrukce umožňují přečíst paměťovou proměnnou do registru a uložit ji zpět. Procesory typu
CISC mívají běžně u aritmetických instrukcí varianty pracující přímo s paměťovým operandem.
Přeložený kód, používající aritmeticko-logické instrukce, vypadá typicky následujícím
způsobem. Nejprve se načtou potřebné hodnoty proměnných z paměti do registrů, provede se
operace a výsledná hodnota se z výsledného registru uloží do paměti.
Pokud se opakuje několik aritmetických operací bezprostředně za sebou, jsou tyto
prokládány instrukcemi načítání proměnných z paměti do registrů a naopak. Pokud některé
instrukce používají stejné operandy, objeví se občas v kódu instrukce, která načítá operand
z paměti do registru, i když je tato hodnota v registru již načtena. Tato instrukce je tedy zbytečná
a jejím vynecháním se nezmění funkčnost programu, viz následující příklad, kde lze zanedbat
třetí instrukci.
00401853
00401855
00401858
0040185A
XOR
MOV
XOR
MOV
EAX,EAX
[DWORD SS:EBP-18],EAX
EAX,EAX
[DWORD SS:EBP-08],EAX
33
89
33
89
C0
45 E8
C0
45 F8
příklad 10 – typický kód aplikačního programu
Zjednodušování operandů
Různé varianty jediné instrukce se mohou lišit délkou kódování a rychlostí spuštění.
Obvykle mají varianty instrukce pracující s registrovými operandy kratší kódování a počet cyklů
na spuštění takové varianty nebývá větší než u variant s paměťovým operandem.
Po překopírování hodnoty z jednoho registru do jiného registru či do paměti je tato
hodnota dostupná dvojím způsobem. Do doby, než je některý z těchto dvou operandů změněn, je
možno zaměňovat ty varianty instrukcí, které pracují s těmito operandy. Překladače si tuto
skutečnost neuvědomují a do výsledného binárního kódu zapisují i instrukce, ke kterým existuje,
co do funkčnosti ekvivalentní, avšak rychlejší nebo kratší varianta.
Následující příklad ukazuje takovou sekvenci kódu, která lze nahradit lepší variantou. Je
nutno poznamenat, že kód lze nahradit i v případě, že mezi první a druhou instrukcí leží další
instrukce, jež nemění hodnoty registrů EAX, EBP a SS, ani hodnotu v proměnné na efektivní adrese
37
[SS:EBP-14] .
00401905
00401908
MOV
CMP
[DWORD SS:EBP-14],EAX
[DWORD SS:EBP-14],00000000
89 45 EC
83 7D EC 00
příklad 11 – optimalizovatelný kód
Sekvenci lze nahradit následovně.
00401905
00401908
MOV
CMP
[DWORD SS:EBP-14],EAX
EAX,00000000
89 45 EC
83 F8 00
příklad 12 – optimalizovaný kód
Nahrazením druhé instrukce se její kódování zkrátilo o čtvrtinu, jako operand zde již
nevystupuje paměťová proměnná, což může na některých typech procesoru instrukci i zrychlit,
přesto však existuje ještě lepší varianta.
37
to však nelze při práci s proměnnou v paměti obecně nikdy zajistit, protože se musí počítat s možností,
že proměnnou pozmění jiné běžící vlákno; spolehlivě napsaný program, však musí být synchronizován
jiným způsobem, a provedené změny ho nemůžou ohrozit
22
Analýza možností optimalizace binárního kódu
00401905
00401908
MOV
TEST
[DWORD SS:EBP-14],EAX
EAX,EAX
89 45 EC
85 C0
příklad 13 – lépe optimalizovaný kód
Zde jsou oba operandy registry, navíc je kódování instrukce ještě kratší.
Redukce počtu používaných operandů
Zaměňování operandů má další příznivý důsledek. Záměny mohou být vedeny záměrně
tak, aby došlo k redukci používaných operandů. Na procesorech typu CISC to velký význam
nemá, ale na procesorech typu RISC, které obsahují velký počet registrů a jsou minimalizovány
přístupy do paměti, to může vést k uvolnění některého registru a jeho použití při dalších
algoritmech optimalizace.
Uvedený algoritmus optimalizace řeší všechny tři zmíněné oblasti. Vypouští zbytečné
instrukce, volí rychlejší varianty instrukcí či varianty s kratším kódováním a snaží se redukovat
počet používaných registrů.
Je nutno definovat ohodnocovací funkci, která vždy pro dva různé operandy (které však
mají stejnou hodnotu) označí jeden z operandů jako optimálnější (vhodnější k použití).
Následující příklad naznačuje, jak může vypadat taková ohodnocovací funkce pro procesory IA32. Binární relace <<opt linearizuje všechny možné operandy, které se mohou vyskytnout
v některé aritmeticko-logické instrukci procesoru.
EAX << optEBX <<opt ... EBP <<opt konstanta <<opt operand v paměti
příklad 14 – seřazení možných operandů dle optimality jejich použití
Přičemž konstanty mohou být ohodnoceny dle své velikosti a operandy v paměti dle
složitosti adresovacího módu (zda-li je použita báze, index, násobící koeficient a posunutí).
Algoritmus je iterován pro každou variantu vstupní instrukce si jednoho svazku instrukcí.
Výstupem je instrukce di. Instrukce di je buď variantou instrukce si (i stejnou), jiná instrukce než
si, nebo prázdná instrukce.
Algoritmus pracuje s několika seznamy Li, které jsou na začátku algoritmu prázdné.
Každý ze seznamů Li je minimálně dvouprvkový a setříděný dle funkce určující optimalitu
operandů. Algoritmus prohlíží jednotlivé instrukce a vytváří si aktuální seznamy operandů Li
se stejnou hodnotou. Zároveň se pokouší vylepšit či odstranit instrukce.
Pokud není znám vliv instrukcí větvení s návratem na hodnoty jednotlivých operandů
v seznamech Li, je nutno tyto seznamy vyprázdnit kdykoli algoritmus narazí na instrukci větvení
s návratem. Pokud je zaručeno, že hodnoty některých operandů zůstávají zachovány i po
instrukci větvení s návratem, nebo, že jsou některé hodnoty kopírovány do jiných operandů, je
možno tuto skutečnost využít v algoritmu při zachování operandů v některém seznamu Li.
1) Analyzuj vstupní instrukci si s operandy op1, op2, ...
2) Pokud si kopíruje hodnotu operandu op2 do operandu op1 (MOV op1,
op2), tak
a) pokud je op1 v nějakém seznamu Li společně s operandem op2, tak
i) výstupní instrukcí di je prázdná instrukce
ii)
skonči
b) pokud existuje seznam Li obsahující operand opj takový, že op1
koliduje s operandem opj, tak
i) vyndej op1 ze seznamu Li, pokud by se tím stal seznam Li
jednoprvkový, tak seznam Li zruš
c) pokud existuje seznam Li s operandem op2, tak
i) zatřiď op1 do tohoto seznamu
ii)
výstupní instrukcí di je instrukce MOV op1, opi, kde opi je
nejmenší operand v seznamu Li dle ohodnocení <<opt
23
Analýza možností optimalizace binárního kódu
3)
4)
5)
6)
iii) skonči
d) pokud neexistuje seznam Li s operandem op2, založ seznam Lj a
zatřiď op1 i op2 do tohoto seznamu
e) výstupní instrukcí di je instrukce si
f) skonči
Pokud si je instrukce, která zapisuje některý z operandů opi, tak
a) pokud existuje seznam Li obsahující operand opj takový, že opi
koliduje s operandem opj, tak
i) vyndej opi ze seznamu Li, pokud by se tím stal seznam Li
jednoprvkový, tak seznam Li zruš
Pokud si je instrukce, která čte některý ze svých operandů opi, tak
a) pokud existuje seznam Li s operandem opi a zároveň existuje
instrukce ei ekvivalentní instrukci si, taková, že obsahuje
operand opj, který je v seznamu Li a zároveň optimálnější než
operand opi, tak
i) výstupní instrukcí di je ta instrukce ei, která má
nejoptimálnější operand ze seznamu Li, ale stále je
ekvivalentní instrukci si
Pokud si je instrukce volající podproceduru (CALL) – podprocedura
může změnit hodnoty operandů, vyprázdni všechny seznamy
skonči
alg. 2 – redukce instrukcí a optimalizace jejich operandů
Pozn. 1: Tento algoritmus může být v některých implementacích na konkrétní procesory
ztížen tím, že nemusí existovat všechny varianty instrukce di pracující se všemi možnými typy
operandů, s touto skutečností musí umět algoritmus počítat a v případě neexistence instrukce di
(sice s lepšími parametry) v dané variantě, musí algoritmus vzít jinou vhodnou variantu instrukce
di (např. původní instrukci si).
Pozn. 2: Výraz „operand op1 koliduje s operandem op2“ lze interpretovat vždy jen
s ohledem na konkrétní procesorovou architekturu a v několika úrovních složitosti implementace
algoritmu. Například na procesorech IA-32, při jednoduché implementaci, kdy algoritmus
rozpoznává, zda-li dva paměťové operandy odkazují do téhož prostoru jen na základě zápisu
jejich efektivních adres, jej lze interpretovat takto:
Operand op1 koliduje s operandem op2 pokud:
1. op1 a op2 jsou stejné
2. op1 i op2 jsou paměťové operandy takové, že nelze s určitostí říci, zda-li se
neodkazují do stejného paměťového prostoru (např. každý operand používá
nepřímé adresování s jinými bázovými či indexovými registry)
3. op1 je registr a op2 je nepřímý paměťový operand takový, že jeho bázový či
indexový registr je shodný s operandem op1
Pozn. 3.: Dále algoritmus nepočítá s variantou, kdy je tentýž operand v jedné instrukci
použit pro čtení i pro zápis (např. inkrementace operandu), tyto případy musí být posuzovány
individuálně.
24
Vnitřní architektura procesorů IA-32
4. Vnitřní architektura procesorů IA-32
Procesorově závislé optimalizace nelze popisovat ani provádět bez důkladných znalostí
vnitřní architektury daného procesoru. Tato kapitola popisuje některé aspekty vnitřní architektury
procesorů IA-32 s podrobnějším popisem u architektur P5 a P6 a navazuje na ni kapitola
o možných optimalizacích procesorů těchto dvou architektur. Popis aplikační architektury
procesorů IA-32 je uveden v dodatku B.
4.1.
Intel 80386
Vnitřní architektura procesoru Intel 80386 se skládá z několika funkčních obvodů.
Načítání instrukcí, jejich dekódování a spouštění, správa paměti a přístup na sběrnici je
realizováno šesti samostatnými obvody, které pracují paralelně. V praxi to znamená, že v jednu
chvíli může být zpracováváno i několik instrukcí současně. Procesor obsahuje obvod realizující
komunikaci mezi pamětí, vstupně/výstupními zařízeními a zbylými obvody procesoru (Bus
Interface Unit), jednotku pro vyzvednutí kódu do 16 bytů dlouhé fronty (Code Prefetch Unit),
jednotku k dekódování instrukce do mikroinstrukcí (Instruction Decode Unit), exekuční
jednotku, která vykoná mikroinstrukce (Execution Unit), jednotku převádějící logický adresový
prostor na lineární (Segment Unit) a jednotku, která převede lineární adresu na fyzickou (Paging
Unit). Tento způsob vykonávání instrukcí se nazývá proudové zpracování instrukcí (Instruction
Pipeling).
4.2.
Intel 80486
Proudové zpracování instrukcí
Oproti předešlé verzi je dekódování a vykonání instrukce rozšířeno do pěti fází – načtení
(Prefetch), dekódování instrukce (Instruction Decode), generování adresy (Address Generate),
vykonání instrukce (Execute) a zapsání výsledků (Writeback). Rozšíření linky proudového
zpracování umožňuje vykonat většinu instrukcí v jediném cyklu procesoru38. Následující obrázek
ukazuje, jak jsou po sobě jdoucí instrukce (značeny I1 – I4) zpracovávány v jednom okamžiku,
každá v jiné fázi.
Načtení
I1 I2 I3 I4
Dekódování
I1 I2 I3 I4
instrukce
I1 I2 I3 I4
Generování adresy
I1 I2 I3 I4
Vykonání instrukce
I1 I2 I3 I4
Zapsání výsledků
t0 t1 t2 t3 t4 t5 t6 t7
Čas
obrázek 2 – proudové zpracování instrukce
I zde patří mezi další nezávisle pracující obvody procesoru jednotky implementující
segmentaci, stránkování, správu vyrovnávací paměti a sběrnice.
38
pouze při dosažení ideálních podmínek – jednoduchá instrukce (např. nikoli násobení či dělení), data
načtena ve vyrovnávací paměti, nekomunikuje s I/O, apod.
25
Vnitřní architektura procesorů IA-32
Vyrovnávací paměť
Integrováním vyrovnávací paměti (Cache) do procesoru je výrazně urychlena práce
s pamětí. Zároveň je však nutno implementovat další mechanismy pro výměnu dat mezi
vyrovnávací a hlavní pamětí. Problematický je především zápis do paměti, poněvadž zápis nelze
obecně vést pouze do vyrovnávací paměti. Např. v případě, že je na dané adrese mapováno
vstupně/výstupní zařízení39 musí být zápis veden až do hlavní paměti. Jsou rozlišovány dva
pojmy – přímý zápis do paměti (Write-Through) a zpožděný zápis do paměti (Write-Back).
Při přímém zápisu se data zapisují skrz vyrovnávací paměť do hlavní paměti, při zpožděném
zápisu se data zapisují až při výměně řádek (1 řádka = 16 bytů) vyrovnávací paměti.
Procesor zavádí možnost určit operačnímu systému, které adresy paměti využívají
vyrovnávací paměť a které ne, použitím dosud rezervovaných bitů v registrech CR0, CR3,
ve stránkovacím adresáři a ve stránkovacích tabulkách (viz odstavec „Stránkování paměti“
v kapitole B.5.2 dodatku B). Pomocí bitů číslo 30 (CD – Cache Disable) a 29 (NW – Not
Writethrough) registru CR0 lze zcela vypnout použití vyrovnávací paměti či nastavit zpožděný
zápis do paměti. Bity číslo 4 (PCD – Page-Level Cache Disable) a 3 (PWT – Page-Level Writes
Transparent) u registru CR3 stránkovacího adresáře a stránkovacích tabulek lze zakázat použití
externí vyrovnávací paměti pro danou stránku, či nastavit způsob zápisu do této stránky.
Vyrovnávací paměť přináší další problémy v případě multiprocesorových systémů, kdy je
nutno zajistit konzistenci a časovou posloupnost zapisovaných a čtených dat. Tato problematika
však přesahuje možnosti předkládané práce a dále není diskutována.
4.3.
Intel Pentium
Vyrovnávací paměť
Procesor integruje pro data i kód dvě vyrovnávací paměti odděleně. Každá z nich je
o velikosti 8kB. Data v této paměti mohou být čtena i zapisována v jediném hodinovém cyklu.
Datová vyrovnávací paměť se skládá z 256 řádek o velikosti 32 bytů. Pokaždé, když se
mají číst data, jež nejsou ve vyrovnávací paměti, procesor načte celou řádku z paměti. Řádky
vyrovnávací paměti jsou vždy zarovnány na fyzickou adresu dělitelnou 32.
Jednotlivé řádky vyrovnávací paměti nemohou uchovávat data z libovolné paměťové
adresy. Každý řádek vyrovnávací paměti má u sebe 7-bitovou hodnotu (Set-Value), existují tedy
vždy dva řádky mající stejnou hodnotu40. Do řádku lze načíst jen ty data ležící ve fyzické paměti,
jejíž adresa se v bitech 5 až 11 shoduje se 7-bitovou hodnotou řádku (bity 0-4 jsou ofsetem
v rámci jedné řádky). Pro výměnu řádek ve vyrovnávací paměti je implementován algoritmus
LRU (Least Recently Used), kdy je uvolněn nejdéle nepoužitý řádek (porovnávají se vždy jen 2
řádky se stejnou hodnotou).
Při zápisu dat na adresu, která není ve vyrovnávací paměti (první úrovně) 41, je zápis
veden přímo do vyrovnávací paměti druhé úrovně či do hlavní paměti (podle toho, jak je
vyrovnávací paměť druhé úrovně nastavena), což je mnohem pomalejší, než zápis do
vyrovnávací paměti první úrovně (záleží na rychlosti paměti a frekvenci procesoru). Procesor má
dvě odkládací paměti (Buffers) k zápisu na adresu, která není uložena ve vyrovnávací paměti.
39
např. grafická karta
tento druh vyrovnávací paměti se nazývá vyrovnávací paměť s částečnou asociativitou stupně 2 (2-Way
Set Associative Cache)
41
interní vyrovnávací paměť první úrovně (Level 1) je integrována přímo na procesoru, externí
vyrovnávací paměť druhé úrovně (Level 2) je dalším stupněm paměti mezi procesorem a hlavní pamětí
40
26
Vnitřní architektura procesorů IA-32
Nedokončený zápis operandu až šířky 64-bitů je uložen zde, díky tomu nemusí být pozdrženo
vykonání následujících instrukcí.
Implementace instrukční vyrovnávací paměti je podobná datové vyrovnávací paměti.
Šířka datové sběrnice je zvětšena na 64 bitů.
Zarovnávání dat a kódu
Všechna data v paměti by měla být zarovnána na adresu dělitelnou 2, 4 či 8 dle tab. 1.
Velikost operandu v bytech
1 (byte)
2 (word)
4 (dword)
Zarovnání
1
2
4
Velikost operandu v bytech
6 (fword)
8 (qword)
10 (tbyte)
Zarovnání
4
8
8
tab. 1 – zarovnání dat na architektuře P5
Pokud je při přístupu do paměti k nezarovnaným datům překročena hranice 4 bytů,
prodlouží se přístup nejméně o 3 hodinové cykly. Penále je vyšší, pokud je zároveň překročena
hranice velikosti řádky ve vyrovnávací paměti (32 bytů).
Nezarovnání kódu nemá vliv na rychlost zpracování.
Proudové zpracování instrukcí
Vnitřní dělení při zpracování instrukce zavedené procesorem Intel 80486 je podobně
řešeno i u procesoru Intel Pentium. Místo původně jediné linky zpracování implementuje Intel
Pentium dvě takové linky označené písmeny u a v. Linka u vykoná libovolnou instrukci
procesoru Intel Pentium, linka v může vykonat pouze některé „jednoduché“ instrukce.
V první fázi (PF – Prefetch) jsou načteny instrukce z vyrovnávací nebo hlavní paměti
(pokud není instrukce přítomna ve vyrovnávací paměti, je odkaz směřován do hlavní paměti, což
značně pozdrží tuto fázi). V případě, že je načtena instrukce větvení, BTB42 zkusí předpovědět,
zda-li ke skoku dojde či nikoliv. Pokud BTB předpoví, že ke skoku nedojde, načítají se instrukce
na následujících adresách, jinak se instrukce načítají na adresách předpovězených BTB. V obou
případech jde o spekulativní načítaní instrukcí, takto částečně zpracované instrukce se nikdy
nemusejí provést (pokud BTB nesprávně předpoví vykonání instrukce větvení).
V další fázi (D1 – Instruction Decode) mohou být, ve dvou dekodérech, dekódovány dvě
následující instrukce. Dekodéry rozhodnou, dle párovacích podmínek, zda-li mohou být obě
instrukce spuštěny současně. Podobně, jako na procesoru Intel 80486, se dekódování instrukce
pozdrží o jeden hodinový cyklus pro každý prefix43 (viz odstavec „Prefixy instrukcí procesorů
IA-16 a IA-32“ kapitoly B.5.1 dodatku B), který instrukce obsahuje. Navíc jsou instrukce
s prefixem43 párovatelné pouze v lince u.
V následující fázi (D2 –Address Generate44) je vypočtena adresa operandů uložených
v paměti. Oproti procesoru Intel 80486 již není tato fáze o jeden hodinový takt pozdržena
v případě, že instrukce používá dvě konstanty (jednu na místě operandu a druhou jako posunutí
42
BTB – Branch Target Buffer, slouží k predikci skoků, která je popisována dále
kromě prefixu 0x0F u instrukcí podmíněného skoku
44
zatímco originální literatura k procesoru Intel 80486 pojmenovává druhou a třetí tuto fázi jako
„Instruction Decode“ a „Address Generate“, literatura k procesoru Intel Pentium rozděluje fázi
dekódování na „Decode 1“ a „Decode 2“, oba páry fází však mají na obou procesorech stejný úkol –
dekódovat instrukci a dekódovat operandy v paměti
43
27
Vnitřní architektura procesorů IA-32
při adresaci druhého operandu) nebo používá k adresaci operandu bázový a indexový registr
současně. Všechny instrukce se na procesoru Intel Pentium zdrží v této fázi právě jeden hodinový
cyklus.
Podobně jako na procesoru Intel 80486 je i na procesoru Intel Pentium ve čtvrté fázi
(EX – Execute) instrukce vykonána v aritmeticko-logické jednotce (ALU – Aritmetic-Logic
Unit) a zároveň se zde přistupuje do datové vyrovnávací paměti (pokud nejsou data ve
vyrovnávací paměti, odkaz směřuje do hlavní paměti, což vyžaduje několik hodinových cyklů
navíc). Z tohoto důvodu může být trvání fáze delší než jeden cyklus.
V poslední fázi (WB – Writeback) jsou výsledky provedené operace zapsány. Tato fáze
modifikuje stav procesoru a ukončuje zpracování instrukce.
Při průchodu instrukce linkou může být její zpracování za jistých podmínek pozdrženo.
Do fáze D1 a D2 vstupují i z nich vystupují instrukce z linek u a v vždy současně. Pokud je jedna
instrukce v některé fázi pozdržena, je pozdržena i druhá instrukce. Do fáze EX tedy obě instrukce
také vstupují současně, ale nemusí současně i vystoupit. Pokud je instrukce v lince u pozdržena,
pozdrží se i instrukce v lince v, naopak to však neplatí, pokud je instrukce v lince v pozdržena,
instrukce z linky u vystoupí z fáze EX dříve. Žádná další instrukce však nemůže do fáze EX
vejít, dokud nejdříve nevystoupí instrukce z obou linek.
Párování celočíselných instrukcí
Schopnost vykonat současně dvě instrukce se nazývá párování. V případě, že je dvě
po sobě jdoucí instrukce možno provést současně, mluví se o párování těchto instrukcí, instrukce
jsou párovatelné.
Instrukce lze rozdělit, dle jejich schopnosti párování, do několika kategorií. Instrukce
mohou být párovatelné v libovolné lince, nebo mohou být párovatelné pouze v jedné konkrétní
lince (a nemohou být v druhé), nebo je nelze párovat vůbec. Dále se dají rozdělit dle toho, zda-li
jsou vůbec v dané lince spustitelné.
Při popisu instrukcí a jejich variant operandů je význam použitých zkratek následující.
reg – registr, mem – paměť, imm – konstanta (přímý operand). Pokud nejsou uvedeny varianty
operandů, pravidlo se vztahuje na všechny varianty instrukce.
Následující instrukce jsou párovatelné v libovolné lince.
MOV reg/mem, reg/mem/imm
PUSH reg/imm
POP reg
TEST reg, reg/mem
TEST AL/AX/EAX, imm
Všechny varianty instrukcí LEA, NOP, INC, DEC, ADD, SUB, CMP, AND, OR a XOR.
tab. 2 – instrukce procesoru Intel Pentium párovatelné v libovolné lince
Následující instrukce jsou párovatelné pouze v lince u.
ADC, SBB
SHR, SAR, SHL, SAL, pokud je druhý operand konstanta
ROR, ROL, RCR, RCL, pokud je druhý operand konstanta rovna jedné
Instrukce s prefixem (viz odstavec „Prefixy instrukcí procesorů IA-16 a IA-32“ kapitoly B.5.1
dodatku B), kromě instrukcí podmíněného skoku s blízkým návěštím
tab. 3 – instrukce procesoru Intel Pentium párovatelné pouze v lince u
Následující instrukce jsou spustitelné v obou linkách, ale párovatelné pouze v lince v.
NEAR CALL (instrukce větvení s návratem, skok na blízké návěští)
SHORT/NEAR JMP (nepodmíněná instrukce větvení, skok na krátké nebo blízké návěští)
28
Vnitřní architektura procesorů IA-32
SHORT/NEAR Jcc (podmíněná instrukce větvení, skok na krátké nebo blízké návěští)
tab. 4 – instrukce procesoru Intel Pentium párovatelné pouze v lince v
Všechny ostatní instrukce (a jejich varianty) nejsou párovatelné a jsou spustitelné pouze
v lince u.
Dvě po sobě jdoucí instrukce jsou vykonány současně pouze v případě, že jsou splněny
následující podmínky.
1. První instrukce je párovatelná v lince u a druhá instrukce je párovatelná v lince v.
2. Druhá instrukce nemůže číst z nebo zapisovat do registru, do kterého zapisovala první
instrukce. Následující příklad ukazuje, kdy se instrukce spárují a kdy ne.
MOV
MOV
EAX, EBX
ECX, EAX
; zápis do registru EAX
; čtení z registru EAX => nepárují
MOV
MOV
EAX, 1
EAX, 2
; zápis do registru EAX
; zápis do registru EAX => nepárují
MOV
MOV
EBX, EAX
EAX,2
; čtení z registru EAX
; zápis do registru EAX => párují
MOV
MOV
EBX, EAX
ECX, EAX
; čtení z registru EAX
; čtení z registru EAX => párují
MOV
INC
EBX, EAX
EAX
; čtení z registru EAX
; čtení z a zápis do registru EAX => párují
příklad 15 – případy párování a nepárování instrukcí procesoru Intel Pentium
3. Se 16-bitovými (AX, …) a 8-bitovými (AL, ….) registry je zacházeno stejně, jakoby
byly 32-bitové (EAX, …), viz příklad.
MOV
MOV
AL, BL
AH, 0
; zápis do registru AL (jako kdyby EAX)
; zápis do registru AH (jako EAX) => nepárují
příklad 16 – zacházení s 16-bitovými a 8-bitovými registry jako s 32-bitovými
4. Pravidlo 2 neplatí pro příznakový registr EFLAGS, viz příklad.
SHR
INC
EAX, 4
EBX
; zápis do registru EFLAGS
; zápis do registru EFLAGS => párují
CMP
JA
EAX, 2
label
; zápis do registru EFLAGS
; čtení z registru EFLAGS => párují
příklad 17 – párování instrukcí, které čtou z a zapisují do registru EFLAGS
5. Dvojice instrukcí PUSH + PUSH, PUSH + CALL, POP + POP se páruje, ačkoli každá
instrukce v páru modifikuje (a tedy zapisuje) registr ESP (opět se porušuje pravidlo 2).
6. Instrukce, která obsahuje ve svém kódování dvě konstanty – jednu jako posunutí
při adresaci a druhou jako zdrojový operand, není párovatelná, viz příklad.
MOV
CMP
CMP
CMP
DWORD PTR DS:[1000], 0 ; dvě konstanty => nepáruje
BYTE PTR [EBX+8], 1
; dvě konstanty => nepáruje
BYTE PTR [EBX], 1
; jedna konstanta => páruje
BYTE PTR [EBX+8], AL
; jedna konstanta => páruje
příklad 18 – instrukce se dvěma konstantami v jejich kódování se nepárují
7. Instrukce, která při adresování paměti používá bázový i indexový registr, není
párovatelná, viz příklad.
MOV
MOV
MOV
DWORD PTR DS:[EAX+2*EBX], 0
DWORD PTR DS:[2*EBX+2], 0
DWORD PTR DS:[EAX+2], 0
; index i báze přítomny => nepáruje
; pouze index => páruje
; pouze báze => páruje
29
Vnitřní architektura procesorů IA-32
příklad 19 – instrukce s bázovým i index. registrem v adresaci operandu nepáruje
8. Obě instrukce musí být načteny a dekódovány. Při prvním spuštění kódu (dokud
nejsou instrukce načteny ve vyrovnávací paměti) procesor při načítání první instrukce
z páru nerozpozná, kde instrukce končí (a tudíž, kde začíná následující), kromě
případu, kdy je první instrukce dlouhá 1 byte. Pro instrukce ve vyrovnávací paměti si
procesor zaznamenává jejich délku od posledního spuštění.
Prodlení při výpočtu adresy (AGI stall)
Při spuštění instrukce přistupující do paměti, stačí procesor obvykle spočítat adresu
v paměti v jediném hodinovém cyklu. Výpočet adresy je proveden v samostatné fázi proudového
zpracování ještě před spuštěním instrukce. Pokud hodnota adresy záleží na výpočtu instrukce
spuštěné v předcházejícím hodinovém cyklu, musí instrukce přistupující do paměti počkat další
jeden cyklus. Tomuto jevu se říká prodlení při výpočtu adresy (AGI stall – Address Generation
Interlock).
Prodlení se vyskytuje i u instrukcí, které implicitně adresují registrem ESP, jako PUSH,
POP, CALL a RET, pokud se ESP měnilo v předcházejícím hodinovém cyklu instrukcemi MOV, ADD
či SUB. Po instrukcích PUSH, POP či CALL se prodlení nevyskytuje45. Po instrukci RET následuje
prodlení pouze v případě, že instrukce obsahuje přímý operand (který se přičítá k ESP). Instrukce
LEA je také ovlivněna prodlením, pokud používá bázový či indexový registr, jehož hodnota se
měnila v předcházejícím cyklu, viz příklady.
ADD
MOV
EBX, 4
EAX, [EBX]
; nastane prodlení
ADD
POP
ESP, 4
ESI
; nastane prodlení
POP
POP
EAX
ESI
; není prodlení
MOV
RET
ESP, EBP
CALL
MOV
L1
EAX, [ESP+8]
; není prodlení
RET
POP
EAX
; není prodlení
RET
POP
8
EAX
; nastane prodlení
INC
LEA
ESI
EAX, [EBX+4*ESI]
; nastane prodlení
; nastane prodlení
příklad 20 – výskyt prodlení při výpočtu adresy
Imperfektní párování
V některých případech nemohou být instrukce tvořící pár spuštěny současně, či se jejich
provedení překrývá jen částečně. Pořád však platí, že první instrukce se provede v lince u a druhá
v lince v, a žádná následující instrukce nemůže být spuštěna, dokud se obě nedokončí.
Imperfektní párování má několik příčin.
1. Pokud je druhá instrukce ovlivněna prodlením.
45
procesor obsahuje speciální obvod, který tuto funkcionalitu zajišťuje
30
Vnitřní architektura procesorů IA-32
2. Dvě instrukce nemohou přistupovat ke stejnému 32-bitovému slovu v paměti
současně. V následujícím příkladu je hodnota registru ESI dělitelná 4.
MOV
MOV
AL, [ESI]
BL, [ESI+1]
; páruje imperfektně, provedení trvá 2 cykly
MOV
MOV
AL, [ESI+3]
BL, [ESI+4]
; páruje perfektně, provedení trvá 1 cyklus
příklad 21 – imperfektní párování při přístupu do stejného dvojslova v paměti
3. Pravidlo 2 je rozšířeno i na případ, kdy obě adresy mají shodné bity 2 až 4 (kolize
při adresaci vyrovnávací paměti). Znamená to tedy, že při perfektním párování
nesmějí být adresy 32-bitových slov dělitelné 32, viz příklad.
MOV
MOV
[ESI], EAX
[ESI+32000], EBX
; páruje imperfektně, provedení trvá 2 cykly
MOV
MOV
[ESI], EAX
[ESI+32004], EBX
; páruje perfektně, provedení trvá 1 cyklus
příklad 22 – imperfektní párování při kolizi adres ve vyrovnávací paměti
4. Spuštění párovatelné celočíselné instrukce, která nepřistupuje do paměti, kromě
špatně predikované instrukce větvení, trvá 1 hodinový cyklus. Pokud instrukce MOV
přistupuje do paměti (čtení nebo zápis) uložené ve vyrovnávací paměti a řádně
zarovnané, trvá její spuštění také 1 cyklus. Vykonání párovatelné instrukce, která čte
z paměti, provede výpočet a zapíše výsledky do registru a příznaků, trvá 2 hodinové
cykly (R/M – read/modify). Vykonání párovatelné instrukce, která čte z paměti,
provede výpočet a výsledky zapíše zpět do paměti trvá 3 hodinové cykly (R/M/W –
read/modify/write). Pokud je R/M/W instrukce párována s R/M nebo R/M/W
instrukcí, je párování imperfektní. Počet cyklů je uveden v následující tabulce.
Druhá instrukce
První instrukce
MOV nebo zápis jen do registru
R/M
R/M/W
1
2
3
MOV nebo zápis jen do registru
2
2
3
R/M
3
4
5
R/M/W
tab. 5 – počet cyklů nutných na provedení dvou párujících instrukcí
ADD
ADD
[mem1], EAX
EBX, [mem2]
; 4 hodinové cykly
ADD
ADD
EBX, [mem2]
[mem1], EAX
; 3 hodinové cykly
příklad 23 – počet cyklů při provedení kombinace dvou R/M a R/M/W instrukcí
5. Pokud se provedení dvou párujících instrukcí zdrží kvůli nepřítomnosti dat
ve vyrovnávací paměti, či kvůli špatnému zarovnání dat, či kvůli špatné predikci
instrukce větvení, potom provedení páru instrukcí trvá déle než doba provedení první
instrukce či doba provedení druhé instrukce, ale trvá kratší dobu než je součet časů
potřebných k provedení obou instrukcí samostatně.
Predikce skoků
Velkým problémem při proudovém zpracování instrukcí jsou instrukce skoku, které mění
sekvenční tok programu. Pokud není na tuto skutečnost procesor připraven, mohou se
momentálně zpracovávané instrukce jevit jako zbytečné, navíc je nutno plnit linku novými
31
Vnitřní architektura procesorů IA-32
instrukcemi, což zpozdí běh programu o dobu plnění linky. Procesor Intel Pentium k omezení
vlivu instrukcí větvení na výkon zpracování implementuje predikci skoku.
Predikcí skoku jsou řízeny všechny instrukce větvení procesoru včetně podmíněných a
nepodmíněných instrukcí, s návratem (CALL) i bez návratu (JMP) i instrukce návratu z procedury
(RET).
Predikce skoku je založena na zvláštní pomocné paměti s rychlým přístupem (BTB –
Branch Target Buffer), kam se ukládá historie několika posledních instrukcí větvení, na základě
čehož je predikce vyhodnocena. BTB je organizována jako částečně asociativní vyrovnávací
paměť stupně 4, kam jsou nové záznamy řazeny pseudo-LRU algoritmem46. BTB umožňuje
uchovávat záznamy až pro 256 různých47 instrukcí větvení, pro hodnotu záznamu (Set-Value)
jsou použity bity 0 až 5 (64 různých hodnot), na jednu hodnotu jsou možné až 4 záznamy. Bity 6
až 31 mají význam štítku (Tag). Každý záznam obsahuje adresu cíle skoku a 4-stavovou hodnotu
(0-3), která označuje pravděpodobnost, zda bude instrukce větvení skutečně provedena či nikoli.
Nižší hodnota znamená nižší pravděpodobnost skoku, 0 navíc znamená, že je záznam nevyužit.
Procesor předpokládá, že instrukce větvení bude provedena, pokud je hodnota položky 2 nebo 3,
jinak skok nepředpokládá. Hodnota záznamu je inkrementována či dekrementována na základě
skutečného provedení či neprovedení skoku (nemůže přerůst přes 3 a podtéci pod 0). Následující
obrázek ukazuje, jak je hodnota záznamu měněna v závislosti na tom, zda byl skok skutečně
vykonán (S – skočeno, N – neskočeno).
N
N
N
N
0
1
2
3
S
S
S
S
obrázek 3 – změna stavu hodnoty záznamu na základě skutečného provedení skoku
Pokud procesor narazí na instrukci větvení, jejíž záznam nemá uložen v BTB, vytvoří
pro ni nový záznam, jehož hodnotu nastaví na 3. Tato vlastnost je zřejmě chybou48 v návrhu
algoritmu predikce skoků, protože ve svém důsledku znamená, že se hodnota záznamu nemůže
dostat z 0 na 1. Ze stavu 0 lze přejít pouze do stavu 3 (pokud je skok proveden; pokud není
proveden, žádný záznam v BTB nevznikne). Autoři zřejmě chtěli zvýhodnit nepodmíněné
instrukce větvení, které se vyskytují často a jsou provedeny vždy. Následkem toho je u instrukcí
větvení, u kterých se skok většinou neprovede, třikrát horší predikce skoků než u instrukcí,
u kterých se skok většinou uskuteční.
Záznam instrukce větvení v BTB je přidělen k adrese instrukce, která je zpracována
v lince u proudového zpracování, předcházejícího páru. Následující příklad ukazuje, jak je
uložen záznam v BTB.
1.
2.
3.
4.
SHR
MOV
CMP
JB
EAX, 1
EBX, [ESI]
EAX, EBX
L
;
;
;
;
páruje
páruje
páruje
páruje
v
v
v
v
lince
lince
lince
lince
u, sem je přidělen záznam BTB
v
u
v
příklad 24 – přidělení záznamu instrukci provedené v lince u předcházejícího páru
První instrukce se páruje s druhou a třetí instrukce se čtvrtou. BTB záznam čtvrté
instrukce je tedy přidělen první instrukci. Když procesor při sekvenčním provádění narazí
46
při výměně položek nemusí být vyřazen nejdéle nepoužitý záznam
tj. na různých adresách
48
v dalších verzích procesoru se již nevyskytuje
47
32
Vnitřní architektura procesorů IA-32
na záznam v BTB s hodnotou 2 nebo 3, přečte si adresu uloženou v záznamu a načte instrukci
na adrese L do linky. Procesor uvedenou činnost provádí ještě před dekódováním čtvrté instrukce
a proto se spoléhá pouze na hodnotu uloženou v položce BTB.
Pokud je skok předpovězen u instrukce větvení špatně, musí procesor vyprázdnit linku
proudového zpracování. Pokud následující pár instrukcí obsahuje také instrukci větvení, procesor
nenačte pro tuto instrukci záznam z BTB, protože nemůže načítat cílovou adresu skoku, když
vyprazdňuje linku. Důsledkem toho je, že u následující instrukce větvení je předpovězeno, že se
skok neuskuteční bez ohledu na to, jaká je hodnota BTB záznamu této instrukce. Hodnota BTB
záznamu u následující instrukce je však aktualizována správně.
Dalším problémem, s po sobě jdoucími instrukcemi větvení, je případ, kdy je mezi
instrukcí větvení A a instrukcí B, které je přidělen BTB záznam instrukce větvení A, ještě jiná
instrukce větvení C, viz následující příklad.
1.
2.
3.
4.
5.
6.
7.
L1:
SHR
MOV
CMP
JB
JMP
MOV
INC
EAX,
EBX,
EAX,
L1
L2
EAX,
EBX
1
[ESI]
EBX
EBX
;
;
;
;
;
;
;
páruje v
páruje v
páruje v
páruje v
nepáruje
páruje v
páruje v
lince
lince
lince
lince
u
v
u
v
lince u
lince v
příklad 25 – následky po sobě jdoucích instrukcí větvení
Při předpokladu, že se neuskuteční skok u 4. instrukce (viz příklad 25), je BTB záznam 5.
instrukce přidělen adrese 3. instrukce. Nechť je navíc předpokládáno, že někdy později procesor
znovu provádí tento kód a skok u 4. instrukce je vykonán. V okamžiku, kdy je čten BTB záznam
5. instrukce, procesor netuší, že následující pár instrukcí neobsahuje instrukci větvení a
predikuje, že pár instrukcí (instrukce číslo 6 a 7) provede skok na L2. Trest za predikování skoku
u nevětvících instrukcí jsou 3 hodinové cykly, navíc je hodnota BTB záznamu 5. instrukce
dekrementována (skok nebyl proveden), což negativně ovlivní predikci 5. instrukce
při následném spuštění kódu (pokud se hodnota BTB záznamu 5. instrukce tímto způsobem
dekrementuje vícekrát až na hodnotu 1 či 0, procesor bude predikovat neprovedení skoku u 5.
instrukce, ačkoli je to instrukce nepodmíněného větvení).
Pokud by se 7. instrukce nahradila instrukcí větvení, nastaví se po provedení skoku u 4.
instrukce 7. instrukci stejný BTB záznam jako 5. instrukci. Sdílení jednoho BTB záznamu dvěmi
instrukcemi větvení opět negativně ovlivňuje algoritmus predikce skoků. Další příklad ukazuje,
že pokud mohou dvě různé instrukce větvení sdílet jeden BTB záznam, může mít jedna instrukce
větvení několik BTB záznamů.
Pokud se nevykoná skok u 1. instrukce, párují se
1.
JZ
L1
2.
MOV
EAX, 1
2. a 3. instrukce spolu a 4. a 5. instrukce spolu. BTB
3.
L1:
MOV
EBX, 2
záznam 5. instrukce větvení je pak přidělen adrese 2.
4.
MOV
ECX, 3
instrukce. Pokud se však skok u 1. instrukce provede,
5.
JC
L2
párují se 3. a 4. instrukce spolu a 5. instrukce zůstane
příklad 26 – více BTB záznamů
nespárovaná. BTB záznam 5. instrukce se pak přidělí
přidělených jediné instrukci větvení
adrese 3. instrukce, takže vzniknou dva BTB záznamy
pro jedinou instrukci větvení.
33
Vnitřní architektura procesorů IA-32
4.4.
Intel Pentium MMX
Vzhledem k tomu, že je vnitřní architektura procesoru Intel Pentium MMX téměř shodná
s Intel Pentium, jsou zde popsány pouze změny oproti staršímu typu.
Vyrovnávací paměť
Velikost vyrovnávací paměti je zdvojnásobena na 16kB pro kód a 16kB pro data.
Vyrovnávací paměť se tedy skládá z 512 řádků, pro každou hodnotu řádku (Set-Value) je tedy
možné alokovat až 4 řádky. Počet odkládacích zápisových pamětí se zdvojnásobil na 4.
Párování instrukcí
Intel Pentium mohl instrukce s prefixem, kromě podmíněných blízkých skoků (NEAR
Jcc), párovat pouze v lince u. Na procesoru Intel Pentium MMX je párovatelná v libovolné lince
kterákoli instrukce s prefixem velikosti operandu, s prefixem velikosti adresy a s prefixem 0x0F.
Instrukce s jiným prefixem je párovatelná pouze v lince u, viz odstavec „Prefixy instrukcí
procesorů IA-16 a IA-32“ kapitoly B.5.1 dodatku B. Instrukce adresující paměť a používající
bázový i indexový registr současně je párovatelná pouze v lince u (předtím nebyla párovatelná
vůbec).
Predikce skoků
Ačkoli částečně vychází mechanismus predikce skoků procesoru Intel Pentium MMX
z předešlého mechanismu procesoru Intel Pentium, je zde patrný velký rozdíl. BTB je nyní
organizováno jako částečně asociativní paměť stupně 16, kde na každý stupeň je možno uložit až
16 záznamů (celkem tedy 256 záznamů). Každý záznam je identifikován bity 2 až 31 adresy
posledního byte instrukce větvení, ke které je BTB záznam přiřazen. Bity 2 až 5 slouží jako
hodnota záznamu (Set-Value), bity 6 až 31 mají úlohu štítku (Tag). Instrukce větvení, které jsou
od sebe vzdáleny 64 bytů sdílí tedy stejnou hodnotu záznamu. BTB záznam je instrukci větvení
přiřazen ve chvíli, kdy u instrukce dojde poprvé ke skoku, do té doby procesor predikuje
pro každou instrukci větvení, že ke skoku nedojde.
Pro podmíněné instrukce skoku (Jcc) procesor implementuje poměrně výkonný predikční
algoritmus, který je schopen sto procentně predikovat instrukce větvení, pokud je výskyt
uskutečněných a neuskutečněných skoků pravidelný až do hloubky 5. Pro nepravidelné výskyty,
či pravidelné výskyty s hlubším zanořením, dosahuje stále dobrých výsledků.
Algoritmus je opět postaven na 2-bitovém čítači, který se inkrementuje, pokud ke skoku
dojde a dekrementuje pokud nikoli, z hodnoty 3 nepřeroste a v hodnotě 0 nepodteče. Každý BTB
záznam má 16 takovýchto čítačů. Dále je v BTB záznamu uložena historie 4 posledních
provedení, či neprovedení skoku dané instrukce větvení (4 bity, kde 0 znamená, že ke skoku
nedošlo a 1, že ano). Ve chvíli, kdy procesor vykoná instrukci větvení, pro niž má vytvořený
záznam v BTB, posune bity v historii o jednu pozici doleva a na uvolněnou pozici zapíše 0,
pokud ke skoku nedošlo a 1, pokud ke skoku došlo. Zároveň inkrementuje či dekrementuje
příslušný čítač číslo 0 až 15, číslo čítače je dáno právě historií skoků.
Např. pokud by se některá instrukce větvení provedla vždy jednou a pak by následovaly
tři případy, kdy se skok neuskuteční, bude její historie 1000 (decimálně 8). Protože vždy
po sekvenci 1000 následuje 1, brzy se čítač číslo 8 dostane na hodnotu 3 a procesor vždy
predikuje, že ke skoku dojde. Čítače číslo 1, 2, a 4 budou na hodnotě 0, zbylé zůstanou
nepoužity. Následující tabulka ukazuje, které všechny vzory historie jsou predikovány perfektně.
délka
vzor historie
1-5
všechny vzory
34
Vnitřní architektura procesorů IA-32
6
7
8
9
10
11
12
13
14
000011, 000101, 000111, 001011
0000101, 0000111, 0001011
00001011, 00001111, 00010011, 00010111, 00101101
000010011, 000010111, 000100111, 000101101
0000100111, 0000101101, 0000101111, 0000110111, 0001010011, 0001011101
00001001111, 00001010011, 00001011101, 00010100111
000010100111, 000010111101, 000011010111, 000100110111, 000100111011
0000100110111, 0000100111011, 0000101001111
00001001101111, 00001001111011, 00010011010111, 00010011101011,
00010110011101, 00010110100111
15
000010011010111, 000010011101011, 000010100110111, 000010100111011,
000010110011101, 000010110100111, 000010111010011, 000011010010111
16
0000100110101111, 0000100111101011, 0000101100111101, 0000101101001111
tab. 6 – perfektně predikované vzory historie
Jsou perfektně predikovány i vzory, které vzniknou převrácením, inverzí a převrácenou
inverzí každého vzoru uvedeného v tabulce. Tj. pokud je perfektně predikován vzor 0001011,
jsou perfektně predikovány i vzory 1101000, 1110100 a 0010111. Dále jsou perfektně
predikovány i ty vzory, které vzniknou rotací o libovolný počet vlevo či vpravo. Zbylé vzory
perfektně predikovány nejsou, ale i tak predikce vykazuje dobré výsledky.
Instrukce skoku (JMP) či volání procedury (CALL) s nepřímým operandem a instrukce
JCXZ a LOOP s libovolným operandem, nejsou predikovány výše uvedeným algoritmem, BTB si
pro ně zaznamenává pouze jedinou (poslední) cílovou adresu. Procesor jednoduše predikuje, že
ke skoku dojde na tuto adresu.
Cílovou adresu instrukce RET, která vrací běh kódu z procedury, je problém predikovat,
protože stejná procedura může být volána z více míst. Z toho důvodu má procesor vytvořen
zvláštní zásobník (RSB – Return Stack Buffer), kam ukládá cílové adresy až 4 instrukcí CALL.
Cílové adresy se pak použijí pro predikce instrukcí RET. Zásobník je typu LIFO (Last In First
Out) a pro správnou predikci je třeba, aby nebyl návrat z procedury implementován jinak, než
za použití instrukce RET. Pokud je zanoření procedur hlubší než 4, jsou výše položené instrukce
RET predikovány jako nepřímé skoky – procesor předpokládá cílovou adresu posledního volání
instrukce.
BTB záznam je identifikován dle bitů 2 až 5 adresy posledního bytu instrukce větvení. To
ovšem znamená, že instrukce větvení, jejichž poslední byte leží ve stejném 32-bitovém slově
paměti, sdílejí jeden BTB záznam, viz příklad.
004010C7
004010CA
CALL
JNC
004019FE
004010DD
E8 00000934 ; poslední byte na 004010C9
73 11
; poslední byte na 004010CB
00401315
00401317
JNZ
RET
0040130E
75 F7
C3
; poslední byte na 00401316
; poslední byte na 00401317
příklad 27 – sdílení stejného BTB záznamu na procesoru Intel Pentium MMX
Sdílení stejného BTB záznamu vede ke špatné predikci obou instrukcí větvení.
Pokud procesor špatně predikuje instrukci podmíněného skoku dekódovanou v lince u,
pozdrží se práce procesoru o 4 hodinové cykly, v případě linky v to je 5 cyklů. Špatná predikce
ostatních instrukcí větvení pozdrží procesor o 4 cykly.
35
Vnitřní architektura procesorů IA-32
4.5.
Intel Pentium Pro
Zde jsou popsány změny v architektuře oproti procesoru Intel Pentium kromě predikce
instrukcí větvení, která je stejná jako u procesoru Intel Pentium MMX.
Zarovnávání dat a kódu
V případě, že se zarovnání dat řídí následující tabulkou, nedojde při přístupu k datové
proměnné ke zdržení. Pokud však nezarovnaná data překročí řádek ve vyrovnávací paměti
(zarovnaných 32 bytů), pozdrží se vykonání instrukce o 6-12 hodinových cyklů.
Velikost operandu v bytech
1 (byte)
2 (word)
4 (dword)
Zarovnání
1
2
4
Velikost operandu v bytech
6 (fword)
8 (qword)
10 (tbyte)
Zarovnání
8
8
16
tab. 7 – zarovnání dat na architektuře P6
Nezarovnání kódu může mít vliv na rychlost zpracování hlavně vzhledem k instrukcím
větvení. Kód je načítán v zarovnaných 16 bytů dlouhých úsecích z vyrovnávací paměti a
umisťován do zdvojené pomocné paměti (Double Buffer), která může obsahovat 2 takové úseky.
Kód je potom dodáván dekodérům po oddílech (Instruction Fetch Block), které jsou obvykle
dlouhé 16 bytů, ale nemusí být zarovnány. Pokud oddíl překračuje hranici 16 bytů, je třeba jej
sestavit z obou úseků v pomocné paměti. Účelem pomocné paměti je tedy umožnit načítání
instrukcí překračujících hranici 16 bytů.
Problém nastane, pokud kód obsahuje instrukci větvení, která překračuje hranici 16 bytů a
cíl instrukce větvení je na instrukci, která také překračuje hranici. V tu chvíli dojde k prodlevě 2
hodinové cykly.
Vyrovnávací paměť
Vyrovnávací paměť první úrovně je prakticky stejná jako u procesoru Intel Pentium.
Integrace vyrovnávací paměti druhé úrovně (L2 cache) do stejného pouzdra s procesorem, i když
na jiném čipu, umožňuje, aby vyrovnávací paměť pracovala se stejným hodinovým kmitočtem
jako procesor, a tak je docíleno vyšší propustnosti mezi procesorem a touto vyrovnávací pamětí.
Navíc výměna dat mezi těmito prvky probíhá po zvláštní sběrnici, základní sběrnice je uvolněna
pro nekolizní spolupráci s hlavní pamětí a zařízeními vstupu a výstupu.
Proudové zpracování instrukcí
Způsob zpracování instrukcí je naprosto odlišný od předcházejících procesorů, celé
zpracování instrukce je rozděleno do 12 fází a umožňuje spekulativní spouštění kódu, zástupné
označení registrů, či změnu uspořádání instrukcí. Z důvodu zpětné kompatibility procesor
implementuje úplnou instrukční sadu předešlých procesorů na procesorovém výkonném jádře
třídy post-RISC49. Vlastní procesor nevykonává přímo původní instrukční sadu (makroinstrukce),
ale interpretuje ji pomocí skutečných instrukcí post-RISC procesoru nazývané mikrooperace
(mikroinstrukce). Každá makroinstrukce je rozložena a buď nahrazena jednou mikroinstrukcí
nebo je interpretována sekvencí několika mikrooperací, které jsou spekulativně vykonávány.
49
takto se značí procesory, které díky změnám v architektuře (proces zpracování instrukce, větší množství
paralelních operací, provádění instrukcí v jiném pořadí), nelze řadit do třídy RISC
36
Vnitřní architektura procesorů IA-32
Instrukce v procesoru probíhají třemi základními stupni (tři nezávislé jednotky) vzájemně
propojenými společným zásobníkem instrukcí (Instruction Pool), viz obrázek 4.
System Bus
L2 Cache
Cache
Bus Interface Unit
L1 Instruction
Cache
Fetch
Fetch/Decode
Unit
L1 Data
Cache
Load
Dispatch/
Execute Unit
Store
Retire
Unit
Intel
Architecture
Registers
Instruction
Pool
obrázek 4 – model procesoru Intel Pentium Pro (architektura P6)
V prvním je instrukce vyzvednuta a dekódována (Fetch/Decode Unit), druhý stupeň
zajišťuje spekulativní provedení mikrooperací (Dispatch/Execute Unit) a třetí stupeň sestavuje
instrukce do původní sekvence (Retire Unit), zde se také zapisují přejmenované registry
do původních registrů procesoru (Intel Architecture Registers). Instrukce se vyzvedávají (Fetch)
z kódové vyrovnávací paměti první úrovně (L1 Instruction Cache) a data se čtou z (Load) či
zapisují do (Store) datové vyrovnávací paměti první úrovně (L1 Data Cache), ty pak komunikují
přes sběrnici rozhraní (Bus Interface Unit) se systémovou sběrnicí (System Bus) či s vyrovnávací
pamětí druhé úrovně (L2 Cache).
Jednotka vyzdvižení a dekódování instrukcí (Fetch/Decode)
Z paměti instrukcí je vyzvednuta odpovídající skupina instrukcí podle toho, jak je
nastaven ukazatel další instrukce. Jeho hodnota je výsledkem zpracování vstupu z vyrovnávací
paměti cílových adres (Branch Target Buffer). Vyzvednutá skupina 16 bytů je předložena
k dekódování následujícímu stupni.
Paralelní dekodéry konvertují instrukce architektury 80x86 do tříadresových mikrooperací
(jedna adresa pro uložení výsledku, dvě adresy pro vstupní operandy). Instrukce jsou převáděny
do jedné ze čtyř mikrooperací. Složitější instrukce jsou předloženy ke zpracování
mikrooperačnímu sekvenceru, který vygeneruje předprogramovanou sekvenci mikrooperací.
Takto zpracované instrukce, nyní již ve formě mikrooperací, jsou předány do jednotky
přidělování přejmenovaných registrů (Registr Alias Table), kde reference vztahující se
k registrům architektury jsou převedeny na přejmenované – fyzické registry mikrooperace.
Alokátor v následujícím stupni přiřadí každé mikrooperaci stavovou informaci, a takto upravenou
mikrooperaci předá do vyrovnávací paměti (Reorder Buffer), která odděluje tuto jednotku
od přidělovací a exekuční jednotky.
37
Vnitřní architektura procesorů IA-32
Procesor obsahuje 3 dekodéry – D0, D1 a D2, které pracují současně. D0 může dekódovat
jakoukoli makroinstrukci, D1 a D2 pouze makroinstrukce, jejichž délka není větší než 7 bytů50, a
které jsou konvertovány na jedinou mikroinstrukci. Pokud makroinstrukce dekódována v D0 má
více jak 4 mikroinstrukce, dekodéry D1 a D2 nemohou produkovat žádné mikroinstrukce. Aby
mohly všechny tři dekodéry produkovat mikroinstrukce současně, musí všechny tři
makroinstrukce ležet v jediném 16 bytů dlouhém oddíle (Instruction Fetch Block).
Přejmenovávání registrů
Přejmenovávání registrů je vyspělá technologie použitá procesorem k odstranění
závislostí mezi registry různých částí kódu, viz příklad 28.
V příkladě jsou poslední tři instrukce nezávislé
1.
MOV
EAX, [mem1]
2.
IMUL EAX, 6
na prvních třech v tom smyslu, že k jejich vykonání nejsou
3.
MOV
[mem2], EAX
třeba výsledky předchozích instrukcí. Optimalizace
4.
MOV
EAX, [mem3]
na starších procesorech by mohla místo registru EAX použít
5.
INC
EAX
jiný registr v posledních třech instrukcích a přerovnat kód
6.
MOV
[mem4], EAX
příklad 28 – nezávislé části kódu tak, aby se vykonával paralelně s prvními třemi.
Na procesorech s architekturou P6 je toto provedeno
automaticky. Jsou alokovány pomocné registry při každém zápisu do registru EAX, tedy 4.
instrukce je nezávislá na předchozích. Při vykonávání instrukcí mimo pořadí je 6. instrukce
vykonána dříve než pomalá 2. instrukce.
Přejmenovávání registrů se děje zcela automaticky, nový pomocný registr je přiřazen
k registru architektury kdykoli, kdy do něj nějaká instrukce zapíše. Instrukce, která činí obojí –
čte i zapisuje stejný registr (např. INC) používá jeden registr pro vstup a jiný registr pro výstup,
toto samozřejmě neodstraní závislosti mezi instrukcemi, ale má to vliv při následném čtení
registru. Všechny obecné registry, ukazatel zásobníku, registr příznaků a segmentové registry
mohou být přejmenovány, procesor vlastní 40 univerzálních pomocných registrů. Procesor
umožňuje přejmenovat až 3 registry v jednom cyklu a vykonat až 3 mikrooperace současně, číst
však může pouze 2 registry architektury v jednom cyklu. To může být vážné omezení, viz
následující příklad.
První instrukce se skládá ze dvou mikrooperací, jedna MOV [EDI+ESI], EAX
čte registr EAX a druhá čte registry EDI a ESI. Druhá instrukce MOV EBX, [ESP+EBP]
obsahuje mikrooperaci, která čte registry ESP a EBP, pokud příklad 29 – čtení příliš mnoha
budou všechny tři mikrooperace současně zpracovávány, registrů architektury
bude zpracování zdrženo o 2 cykly.
Přidělovací a exekuční jednotka (Dispatch/Execute)
Přidělovací jednotka vybírá mikrooperace z vyrovnávací paměti podle jejich stavové
informace. Jestliže tato stavová informace indikuje, že mikrooperace má k dispozici všechny
potřebné operandy, je uložena do rezervační stanice a přidělovací jednotka následovně zjišťuje,
zda jsou k dispozici další zdroje pro zpracování mikrooperace (např. volné funkční jednotky).
Pokud jsou jak operandy, tak další zdroje k dispozici, mikrooperace je předána z rezervační
stanice příslušné exekuční jednotce, kde je zpracována. K rezervačním stanicím jsou exekuční
jednotky připojeny prostřednictvím pěti bran (číslo 0 až 4). Jednoduché aritmeticko-logické
instrukce mohou být vykonány v jednotce brány 0 nebo 1, podle toho, která je volná dříve. Brána
0 dále navíc obslouží násobení, dělení, celočíselné rotace a posuvy. Brána 1 zase zvládne
50
8 bytů na ostatních procesorech s architekturou P6
38
Vnitřní architektura procesorů IA-32
instrukce větvení. Jednotka na bráně 2 provede čtení z paměti, brána 3 vypočte adresu pro zápis
do paměti a pomocí brány 4 lze zapisovat do paměti.
Jednotlivé instrukce a výčet mikrooperací, ze kterých se skládají, je uveden např.
v [INTL97b] či [FOG00], zde je vypsáno několik nejčastějších variant.
Zápis registru do paměti je tvořen ze dvou mikrooperací. První mikrooperace zapíše
registr (brána 4), druhá mikrooperace vypočte adresu zápisu z obecného registru, který do paměti
ukazuje (brána 3).
Instrukce čtení z paměti a modifikace registru některou aritmeticko-logickou operací se
skládá ze dvou mikrooperací. První mikrooperace přečte operand z paměti (brána 2), druhá
mikrooperace je modifikující operace, která čte a zapisuje cílový registr a případně i příznakový
registr (brána 0 či 1).
Dekódování R/M/W instrukce generuje čtyři mikrooperace. První mikrooperace načte
operand z paměti (brána 2), druhá mikrooperace čte zdrojový operand (registr či konstanta),
provede operaci a zapíše výsledky do pomocného registru a příznaků (brána 0 či 1), třetí
mikrooperace přečte a zapíše pomocný registr (brána 4) a čtvrtá mikrooperace spočte adresu
zápisu.
Instrukce PUSH reg se skládá za tří mikrooperací. První přečte zdrojový registr a zapíše
jeho hodnotu do paměti (brána 4), jejíž adresu spočte druhá mikrooperace přečtením hodnoty
registru ESP (brána 3). Třetí mikrooperace odečte velikost ukládaného operandu od hodnoty
registru ESP (brána 0 či 1).
Vyzdvižení operandu ze zásobníku (POP reg) je složeno ze dvou mikrooperací. První
mikrooperace přečte hodnotu registru ESP a načte uložený operand do registru (brána 2). Druhá
mikrooperace upraví hodnotu registru ESP o velikost hodnoty načteného slova (brána 0 či 1).
Odkládací jednotka (Retire Unit)
Mikrooperace provedená exekuční jednotkou mimo pořadí je předána do vyrovnávací
paměti odkládací jednotky, kde vyčkává, než dorazí mikrooperace, které měly být vykonány
podle pořadí programových kroků dříve, výsledky instrukcí jsou zveřejňovány v pořadí, jak jsou
uvedeny v kódu. Odkládací jednotka může zpracovat až 3 mikroinstrukce v jednom hodinovém
cyklu, instrukce větvení musí být navíc v daném cyklu zpracovány jako první. Vyrovnávací
paměť může obsahovat až 40 mikrooperací, to je dostatečný počet pro vykonání několika
instrukcí následujících po opravdu pomalé instrukce (např. dělení). V případě spekulativního
spouštění kódu u odhadovaných instrukcí větvení nemohou být výsledky spekulativně
spuštěných instrukcí zveřejněny, dokud není jisté, že predikce byla správná. Zápisy do paměti,
přístup ke vstupně/výstupním zařízením a serializační instrukce nemohou být spuštěny
spekulativně.
Poměrně dlouhá linka proudového zpracování dat, 12 stupňů, má jednotlivé stupně
zjednodušeny, takže potřebný čas na jeden stupeň je asi o 30% kratší než u procesoru Intel
Pentium. To umožňuje zvýšení hodinového kmitočtu procesoru.
Prodlení při zápisu do částečného registru (Partial stalls)
Pokud po instrukci, která zapisuje do 16-bitové či 8-bitové části registru následuje
instrukce, která čte celý registr nebo jeho větší část, dojde k prodlení, viz příklad.
1.
2.
MOV
MOV
AL, BYTE PTR [mem1]
EBX, EAX
; zápis do části registru
; nastane prodlení
příklad 30 – vznik prodlení při zápisu do části registru
39
Vnitřní architektura procesorů IA-32
Pomocný registr je v první instrukce přidělen registru AL (který je nezávislý na AH) a
při vykonávání druhé instrukce musí exekuční jednotka počkat, než se hodnota z pomocného
registru zapíše do registru AL, teprve pak může hodnotu z AL zkombinovat se zbylými bity
registru, velikost prodlení je 5-6 hodinových cyklů. K prodlení nedojde, pokud se čte část
registru před instrukcí, která do registru zapisovala (či do jeho větší části). Následující příklad
ukazuje, kdy k prodlení dojde a kdy nikoli.
MOV
ADD
INC
BH, 0
BX, AX
EBX
; nastane prodlení
; nastane prodlení
MOV
ADD
ADD
MOV
MOV
EAX, [mem32]
BL, AL
BH, AH
CX, AX
DX, BX
;
;
;
;
není prodlení
není prodlení
není prodlení
nastane prodlení
příklad 31 – eliminace prodlení při zápisu do celého registru
Prodlení při zápisu registru příznaků (Partial flags stalls)
K prodlení dojde také při zápisu do registru příznaků a následném čtení některých bitů,
viz příklad.
1.
2.
3.
CMP
INC
JBE
EAX, EBX
ECX
L
; nastane prodlení
příklad 32 – výskyt prodlení při zápisu a čtení registru příznaků
Třetí instrukce čte příznak přenosu (Carry Flag) a příznak nulového výsledku (Zero Flag).
Protože však druhá instrukce mění pouze příznak nulového výsledku a ponechává příznak
přenosu, musí třetí instrukce počkat na obě předchozí instrukce, než se zkombinují jejich
výsledky.
Prodlení se dále vyskytuje u instrukcí, které čtou mnoho nebo všechny příznakové
registry, jako LAHF, PUSHF či PUSHFD. Následující instrukce zapříčiní výskyt prodlení, pokud jsou
následovány instrukcemi LAHF či PUSHF(D): INC, DEC, TEST, instrukce bitových testů a bitového
prohlížení, CLC, STC, CMC, CLD, STD, CLI, STI, MUL, IMUL, bitové posuvy a rotace. Instrukce AND,
51
OR, XOR, ADD, ADC, SUB, SBB, CMP a NEG nezapříčiní výskyt prodlení . Následují příklady, kdy
k prodlení dojde a kdy nikoli.
INC
EAX
PUSHFD
; nastane prodlení
ADD
EAX, 1
PUSHFD
; není prodlení
SHR
EAX, 1
PUSHFD
; nastane prodlení
SHR
EAX, 1
OR
EAX, EAX
PUSHFD
; není prodlení
TEST
LAHF
; nastane prodlení
EBX, EBX
51
zvláštní je, že se instrukce TEST a AND chovají odlišně, ačkoli k příznakovému registru obě přistupují
stejně
40
Vnitřní architektura procesorů IA-32
AND
LAHF
EBX, EBX
TEST
SETZ
EBX, EBX
AL
; není prodlení
CLC
SETZ
AL
; nastane prodlení
CLD
SETZ
AL
; není prodlení
; není prodlení
příklad 33 – další příklady, kdy dojde k prodlení a kdy nikoli
Prodlení po instrukcích bitového posunu a rotace
Podobné prodlení, které se vyskytuje při zápisu do registru příznaků, nastane i v případě,
kdy následuje instrukce, která čte libovolný bit příznakového registru po instrukci bitového
posunu či rotace, kromě varianty, kdy je velikost posunu či rotace konstanta rovna jedné, viz
příklad.
SHR
JZ
EAX, 1
L
; není prodlení
SHR
JZ
EAX, 2
L
; nastane prodlení
SHR
OR
JZ
EAX, 2
EAX, EAX
L
; není prodlení
SHR
JC
EAX, 5
L
; nastane prodlení
SHR
SHR
JC
EAX, 4
EAX, 1
L
; není prodlení
SHR
JZ
EAX, CL
L
; nastane prodlení i kdyby CL bylo 1
SHRD
JZ
EAX, EBX, 1
L
; nastane prodlení
ROL
JC
EBX, 8
L
; nastane prodlení
příklad 34 – výskyt prodlení po instrukci posunu či rotace
Velikost prodlení jsou v tomto případě asi 4 hodinové cykly.
Prodlení při kombinaci různě velkých operandů na stejné adrese paměti
Pokud instrukce zapíše na adresu v paměti operand a následující instrukce ze stejné
adresy přečte operand jiné velikosti, dojde k prodlení, protože procesor musí zkombinovat
zapsanou a původní hodnotu. Podobně dojde k prodlení, pokud po zápisu velkého operandu
následuje čtení operandu menší velikosti, kdy částečný operand nezačíná na stejné adrese.
Prodlení nastane i v případě, kdy je přistupováno na dvě různé adresy, které však sdílejí stejnou
hodnotu řádky ve vyrovnávací paměti (Set-Value). Následující příklad demonstruje výskyt
prodlení v uvedených případech.
MOV
MOV
BYTE PTR [ESI], AL
EBX, DWORD PTR [ESI]
; nastane prodlení
41
Vnitřní architektura procesorů IA-32
MOV
MOV
MOV
DWORD PTR [ESI], EAX
BL, BYTE PTR [ESI]
BH, BYTE PTR [ESI+1]
; není prodlení
; nastane prodlení
MOV
MOV
MOV
BYTE PTR [ESI], AL
EBX, DWORD PTR [ESI+4092]
ECX, DWORD PTR [ESI+4096]
; není prodlení
; nastane prodlení
příklad 35 – prodlení při kombinaci různě velkých operandů na stejné adrese paměti
Velikost prodlení je přibližně 7 – 8 hodinových cyklů.
4.6.
Shrnutí
Důkladné znalosti architektury procesorů IA-32 jsou základním předpokladem
pro následující kapitolu zabývající se možnými procesorově závislými optimalizacemi nad těmito
procesory. Následující tabulka shrnuje některé vlastnosti procesorů architektur P5 a P6.
Vlastnost
Pentium PMMX PPro
PII
PIII
kódová vyrovnávací paměť, kB
8
16
8
16
16
datová vyrovnávací paměť, kB
8
16
8
16
16
vestavěná vyrovnávací paměť 2. úrovně, kB
0
0
256
51252
51252
MMX instrukce
ne
ano
ne
ano
ano
XMM instrukce
ne
ne
ne
ne
ano
instrukce podmíněného přesunu
ne
ne
ano
ano
ano
vykonávání instrukcí mimo původní pořadí
ne
ne
ano
ano
ano
špatná
dobrá
dobrá
dobrá
dobrá
256
256
512
512
512
velikost zásobníku adres volání procedur (RSB)
0
4
16
16
16
zdržení při nesprávné predikci instrukce větvení
3-4
4-5
10-20
10-20
10-20
zpoždění při zápisu do částečného registru
0
0
5
5
5
počet cyklů na vykonání instrukce FMUL
3
3
5
5
5
zpoždění před následující instrukcí FMUL
1
1
1
1
1
počet cyklů na vykonání instrukce IMUL
9
9
4
4
4
zpoždění před následující instrukcí IMUL
8
8
0
0
0
predikce instrukcí větvení
počet položek v paměti historie skoků
tab. 8 – vlastnosti procesorů Intel Pentium a vyšších
Mimo popsaných celočíselných instrukcí procesoru, je možno zapojit i koprocesor
pro počítání s reálnou aritmetikou (FPU – Floating Point Unit), obvykle označovaný dle typu
hlavního procesoru53 8087, 80187, 80287, 80387 a 80487 (od procesoru Intel Pentium je
52
procesor Celeron má 0-128 kB, Xeon 512kB nebo více a existují i další varianty
CPU však může spolupracovat i s nižší verzí FPU, např. procesor Intel 80386 s koprocesorem Intel
80287
53
42
Vnitřní architektura procesorů IA-32
koprocesor vždy integrován spolu s hlavním procesorem54). Ačkoli možnosti optimalizace jsou
široké i na úrovni FPU instrukcí, nejsou zde popisovány FPU instrukce, ani nad nimi možné
optimalizace, ani jiné vlastnosti s FPU související. Pro další informace o FPU a jeho instrukcích
viz [INTL99a] a [CRVV92], pro jejich možné optimalizace viz [INTL97b] , [INTL99d] a
[FOG00].
Instrukce multimediálního rozšíření procesorů Intel Pentium MMX, II, III a IV zde také
nejsou popisovány, informace o nich a o jejich optimalizacích viz reference k literatuře o FPU.
54
některé verze procesoru Intel 80486 měly integrován FPU
43
Procesorově závislé optimalizace kódu procesorů IA-32 architektury P5 a P6
5. Procesorově závislé optimalizace kódu procesorů IA-32
architektury P5 a P6
V předešlé kapitole jsou uvedeny některé aspekty systémové architektury procesorů IA32, která dodržuje programovou kompatibilitu s procesory IA-16. V této kapitole jsou
popisovány optimalizace procesorů IA-32, které využívají popsaných vlastností architektur P5 a
P6. Některé popisované optimalizace se však dají aplikovat na celou množinu procesorů IA-32.
Je zřejmé, že díky složitosti procesorů IA-32, se objevuje mnoho možností, jak
optimalizovat binární kód. Vzhledem k omezením, které jsou dány nemožností dostatečné
analýzy binárního kódu, lze bohužel jen některé z možných optimalizací provádět
automatizovaně (programem). Popis uvedených optimalizací je rozdělen do několika kapitol,
u každé optimalizace je uvedeno, zda-li a jakým způsobem je možno danou optimalizaci
aplikovat programově. Jsou sledovány dvě problematiky – zda-li je možno daný neoptimální jev
detekovat a zda-li je možno jej optimalizovat. Další otázkou samozřejmě je, jak velký vliv bude
mít daná optimalizace na rychlost vykonávaného kódu.
Popisované optimalizace jsou adresovány všem procesorům (Intel Pentium, Intel Pentium
MMX, Pro, II a III). V případě, že je optimalizace určena jen některému z nich, je uveden odkaz
na architekturu P5 v případě procesorů Intel Pentium a Intel Pentium MMX a na architekturu P6
v případě ostatních procesorů. Případně je odkaz veden na konkrétní procesor.
5.1.
Vhodné zarovnávání
5.1.1. Zarovnávání dat
Pro optimální přístup procesoru k datům je třeba, aby data byla zarovnána na určitou
adresu. Velikost zarovnání záleží na velikosti přistupovaného operandu, tab. 1 a tab. 7 ukazují,
jak nejlépe zarovnávat operandy konkrétních velikostí.
Přesunutím konkrétního paměťového operandu v datové sekci programu se musí
pozměnit příslušným způsobem i instrukce k němu přistupující. To je však problém v případě
nepřímých adres operandu, které jsou spočteny až za běhu programu55. Tento důvod prakticky
zabraňuje možnosti provádět takovouto optimalizaci automatizovaně.
Automatické zarovnávání však nemusí být takový problém v případě lokálních dat
uložených na zásobníku. Stačí zarovnat registr požívaný k adresaci lokálních dat, typicky EBP,
viz příklad.
PUSH
MOV
AND
SUB
...
ADD
POP
RET
EBP
EBP, ESP
EBP, -8
ESP, L + 4
; vytvoření rámce
; zarovnání ukazatele v rámci na 8 bytů
; alokování prostoru o velikosti L
ESP, L + 4
EBP
; dealokace prostoru
; zrušení rámce
příklad 36 – zarovnání lokálních dat
55
duplikaci dat, tak jako v případě kódové sekce, viz odstavec „Duplikace kódu“ kapitoly 3.4.4, zde
samozřejmě nelze realizovat
44
Procesorově závislé optimalizace kódu procesorů IA-32 architektury P5 a P6
5.1.2. Zarovnávání kódu
Na procesoru Intel Pentium (ani MMX) nemá nezarovnaný kód vliv na rychlost jeho
vykonání. Na procesorech architektury P6 to však vliv má. Částečně lze eliminovat zdržení
vykonávaného kódu zarovnáním návěští nepodmíněné instrukce větvení (CALL, JMP) a instrukce
LOOP na 16 bytů, pokud je instrukce vzdálena méně než 8 bytů od adresy dělitelné 16.
Dalším důvodem k zarovnání některých instrukcí může být eliminace sdílení stejné BTB
položky, přinucení procesoru k párování určitých instrukcí (na P5), dekódování některých
instrukcí současně (na P6) apod. Ke splnění vhodného zarovnání instrukcí se nabízí několik
řešení, nejoptimálnější je kód přerovnat (pokud tomu nebrání jiné optimalizační záměry). Pokud
nelze kód přerovnat, je nutno vložit mezi původní instrukce některé další, např. vhodný počet
instrukcí NOP (NO Operation). Instrukce NOP neprovede nic, ale její vykonání trvá 1 cyklus, což
není zcela optimální. Pokud je třeba vyplnit prázdnými instrukcemi delší úsek, lze použít i delší
„prázdné“ instrukce, viz příklad.
NOP
MOV
LEA
LEA
LEA
LEA
EAX, EAX
EAX,[BYTE
EAX,[BYTE
EAX,[BYTE
EAX,[BYTE
DS:EAX+00]
DS:EAX+00]
DS:EAX+00000000]
DS:EAX+00000000]
90
89 C0
8D 40 00
3E> 8D 40 00
8D 40 00 00 00 00
3E> 8D 40 00 00 00 00
;
;
;
;
;
;
1
2
3
4
6
7
byte
byty
byty
byty
bytů
bytů
příklad 37 – prázdné instrukce
Provedení všech výše uvedených instrukcí trvá vždy jen 1 cyklus a jsou párovatelné
v lince u i v (na P5). Uvedením prefixu 0x3E56 se prodlouží dekódování o 1 cyklus na P557.
Všechny instrukce generují jedinou mikrooperaci na P6, která je v případě NOP a MOV spustitelná
v exekuční jednotce 0 či 1, v případě LEA jen v exekuční jednotce číslo 0.
Vkládání prázdných instrukcí vždy znamená provést tyto instrukce a tím i zhoršení
optimality kódu. Lepším řešením (ne vždy uskutečnitelným) se jeví vložit různé „výplně“, které
sice prodlouží velikost kódu, ale ponechají dobu potřebnou na jeho vykonání, typicky to je
zvolení jiné varianty instrukce či použití redundantního prefixu jako v minulém příkladě. Mnoho
instrukcí (aritmeticko-logické, větvení aj.) má totiž více způsobu kódování, lze použít místo 8bitových 32-bitové operandy, dále lze některé instrukce mezi sebou zaměňovat apod., viz tab. 9.
Vzhledem k dynamickému toku kódu lze stoprocentně úspěšné automatické optimalizace
dělat jen stěží.
5.2.
Optimální využití vyrovnávací paměti
Optimální využití vyrovnávací paměti (kódové i datové) podstatně urychluje vykonání
kódu. Kód vykonávaný v cyklu, který je celý umístěn v kódové vyrovnávací paměti, potřebuje
na vykonání jednoho cyklu několikanásobně kratší dobu, než kód, jehož velikost přesahuje
velikost vyrovnávací paměti. Z tohoto důvodu lze konstatovat, že kratší kód obecně potřebuje
k vykonání kratší dobu. Z podobného důvodu je záhodno umisťovat vedle sebe procedury, které
jsou používány společně a často volány. Zkracování kódu i jeho přemisťování lze provádět
automatizovaně.
56
změna implicitního segmentového registru na DS, nevadí, že implicitní registr již je registr DS
v případě, že instrukce dekódovaná v lince u obsahuje prefix a instrukce v lince v nikoli, trvá
dekódování na procesoru Intel Pentium MMX jen 1 cyklus
57
45
Procesorově závislé optimalizace kódu procesorů IA-32 architektury P5 a P6
Sledovat využití datové vyrovnávací paměti lze (vzhledem k dynamičnosti přístupu)
mnohem hůř. Při provádění určité operace nad velkým počtem prvků je výhodnější, leží-li tyto
prvky těsně vedle sebe. Např. v případě velkého počtu záznamů (Record) uložených v poli
(Array), kde každý záznam se skládá z několika datových položek (Field) a kdy hromadně
prováděné operace se vždy týkají jen jedné položky v záznamu, je optimálnější rozdělit pole
na několik samostatných polí. V každém poli pak bude přítomen jen daný typ položek58.
Nebezpečnost nedodržení tohoto přístupu demonstruje následující příklad.
1. L: MOV
2.
MOV
3.
MOV
...
4.
DEC
5.
JNZ
EAX, [ESI]
EBX, [ESI + 13*4096 + 4]
ECX, [ESI + 20*4096 + 28]
; hodnota řádku (Set-Value)
; vyr. paměti, odkud je
; čteno, je stejná!!
EDX
L
příklad 38 – čtení dat sdílejících stejný řádek ve vyrovnávací paměti
Úseky paměti (velikosti 32 bytů), které jsou od sebe umístěny v násobcích 4096 bytů,
mají stejnou hodnotu řádku (Set-Value) ve vyrovnávací paměti. Vyrovnávací paměť procesorů
Intel Pentium a Intel Pentium Pro může vždy obsahovat maximálně dva řádky se stejnou
hodnotou. Z tohoto důvodu dojde při každém provedení cyklu k načtení nové řádky do
vyrovnávací paměti, což nesmírně zpomalí vykonání cyklu.
Pokud se 3. instrukce nahradí instrukcí
MOV
ECX, [ESI + 20*4096 + 32],
načte se operand z následující řádky vyrovnávací paměti a není nutné načítat data z hlavní
paměti, vykonání těla cyklu se podstatně urychlí. V praxi se takovýto problém objeví jen
výjimečně (jde o vykonstruovaný příklad), příklad však ukazuje, že organizace datové struktury
může podstatně ovlivnit přístup k jejím položkám.
Automatizovaná optimalizace datových struktur vzhledem k optimálnímu využití
vyrovnávací paměti je ze zřejmých příčin prakticky neproveditelná.
5.3.
Změna pořadí instrukcí
Některé zdržující jevy lze eliminovat zpřeházením instrukcí a docílit tím
optimalizovanějšího kódu. Tyto optimalizace nemívají vliv na velikost kódu. Mezi neoptimální
jevy lze na P5 zařadit výskyt prodlení při výpočtu adresy (AGI Stall) a imperfektní párování
sousedních instrukcí. Na architektuře P6 lze zase přeházením instrukcí zvýšit propustnost
jednotky v některých fázích zpracování instrukce.
5.3.1. Eliminace prodlení při výpočtu adresy
Prodlení jeden hodinový cyklus vznikající při přístupu na adresu, jejíž hodnota se teprve
počítá v předcházejícím cyklu, lze eliminovat vhodným zpřeházením či změnou instrukcí.
ADD
MOV
EBX, 4
EAX, [EBX]
; nastane prodlení
MOV
ADD
EAX, [EBX+4]
EBX, 4
; nenastane prodlení
příklad 39 – eliminace prodlení
58
opět jde o to, umístit prvky, k nimž je současně přistupováno, co nejblíže k sobě
46
Procesorově závislé optimalizace kódu procesorů IA-32 architektury P5 a P6
Podobného efektu lze dosáhnout, pokud se mezi první a druhou instrukci vloží jiná
instrukce nemající vliv na původní instrukce.
5.3.2. Eliminace imperfektního párování
Některé kombinaci instrukcí nemohou párovat perfektně (u P5), jejich provedení není
současné a počet cyklů na provedení těchto instrukcí je větší než by byl při jejich perfektním
spárování. Přeskupením instrukcí či vložením prázdné instrukce (NOP) lze docílit lepších
výsledků.
MOV
MOV
INC
EAX, [ESI]
EBX, [ESI]
ECX
; linka u
; linka v (přístup na stejnou adresu => imp. párování)
; linka u, celkem 3 hodinové cykly
příklad 40 – imperfektní párování z důvodu přístupu na stejnou adresu
Přehozením druhé a třetí instrukce se odstraní imperfektní párování a doba provedení se
zkrátí na 2 hodinové cykly.
1. L: MOV
2.
XOR
3.
INC
4.
MOV
5.
JMP
EAX, offset A
EBX, EBX
EBX
ECX, [EAX]
L
;
;
;
;
;
linka
linka
linka
linka
linka
u, výpočet adresy A
v
u
v, prodlení => imp. párování
u, celkem 4 hodinové cykly
příklad 41 – imperfektní párování z důvodu výskytu prodlení
Vložením instrukce NOP (nebo jiné instrukce s podobným efektem) mezi 3. a 4. instrukci
se bude 3. instrukce perfektně párovat s instrukcí NOP a 4. a 5. instrukce perfektně spolu, celkový
čas se zkrátí o jeden hodinový cyklus.
Výskyt prodlení při výpočtu adresy (AGI Stall) i imperfektní párování je možno
detekovat pouze v případě, že je znám přesný způsob párování jednotlivých instrukcí. To však
vzhledem ke složitosti určení, která instrukce se bude párovat s kterou (často to je, např.
v případě cyklu, různé pro každou iteraci cyklu), nemusí být vždy možné zautomatizovat. Na
druhou stranu, zpřeházení instrukcí lze provést i bez znalosti toho, zda-li k prodlení či
imperfektnímu párování dochází.
5.3.3. Zvýšení propustnosti dekodéru
Procesory architektury P6 obsahují 3 dekodéry, které převádí makroinstrukce architektury
do mikrooperací, které pak procesor vykonává. První dekodér umožňuje dekódovat jakoukoli
instrukci, druhé dva dekódují pouze jednoduché instrukce, které generují jednu mikrooperaci.
V některých případech lze přeskupením makroinstrukcí dosáhnout lepší propustnosti instrukcí
skrz fázi dekódování, viz následující příklad.
1.
2.
3.
4.
MOV
INC
ADD
ADD
EBX, [mem1]
EBX
EAX, [mem2]
[mem3], EAX
;
;
;
;
1
1
2
4
mikrooperace
mikrooperace
mikrooperace
mikrooperace
(D0)
(D1)
(D0)
(D0)
příklad 42 – dekódování 4 instrukcí ve 3 cyklech (P6)
Pokud budou všechny instrukce v jednom 16 bytů dlouhém oddíle, dekóduje procesor
tento kód ve 3 cyklech, přeskupením instrukcí do pořadí 3, 1, 2, 4 se ušetří jeden cyklus.
K efektivnímu zvýšení propustnosti kódu je třeba znát nejen umístění jednotlivých
instrukcí (kvůli 16 bytům dlouhým oddílům), ale i každou trojici, která vstupuje do dekódovací
47
Procesorově závislé optimalizace kódu procesorů IA-32 architektury P5 a P6
fáze společně. Tyto dvě podmínky značně ztěžují automatickou optimalizaci využívající výše
popsaného způsobu.
5.3.4. Podmínky pro přerovnání kódu
Před změnou pořadí instrukcí je třeba ověřit, zda-li instrukce splňují určité podmínky.
Jednou z podmínek je splnění vzájemného vztahu ve všech operandech, ke kterým instrukce
přistupují. Před uvedením podmínek pro přerovnání kódu jsou zavedeny následující pojmy.
Dřívější a pozdější instrukce ve svazku
Pokud jsou instrukce I1 a I2 součástí stejného svazku instrukcí, je I1 dřívější instrukce a
I2 pozdější instrukce právě tehdy, když adresa instrukce I1 je numericky menší než adresa
instrukce I2.
Vzájemný vztah dvou instrukcí k operandu
Pokud instrukce I1 a I2 jsou ve stejném svazku a obě přistupují k operandu O, pak jsou
ve vzájemném vztahu k operandu O. Instrukce mohou být vždy v právě v jednom z následujících
čtyř vztahů – R/R, W/R, R/W a W/W (Read/Write). Při předpokladu, že je I1 dřívější instrukce
než I2, jsou instrukce I1 a I2 ve vztahu R/R, pokud obě instrukce čtou operand O; jsou ve vztahu
R/W, pokud instrukce I1 čte operand O a instrukce I2 jej zapisuje; jsou ve vztahu W/R, pokud
instrukce I1 zapisuje operand O a instrukce I2 jej čte; a jsou ve vztahu W/W, pokud operand O
obě instrukce zapisují.
Závěr
Je zřejmé, že obecně lze prohodit jen ty instrukce, které leží v jednom svazku a které jsou
ve vztahu R/R ke všem operandům, ke kterým obě instrukce přistupují. Navíc nelze prohodit
některé další instrukce, jako např. instrukce pracující se vstupně/výstupním zařízením apod.
Problematické mohou být instrukce zapisující do paměti, hlavně v souvislosti
s multiprocesovými a multiprocesorovými systémy, samostatnou kapitolou jsou privilegované
instrukce.
5.4.
Dělení komplexních instrukcí
Některé komplexní instrukce procesorů IA-32 lze nahradit sekvencí několika instrukcí,
kde celkový čas potřebný k jejich provedení je kratší, než čas potřebný k provedení původní
instrukce. Tyto optimalizace obvykle prodlužují velikost kódu.
5.4.1. Méně cyklů potřebných k vykonání instrukce
V následujícím příkladu (jen pro P5) je za blokem (neoptimalizovaných) instrukcí uveden
blok optimálnějších instrukcí produkujících však stejné výsledky.
ADD
ADD
[mem1], EAX
[mem2], EBX
; dvě R/M/W instrukce => celkem 5 cyklů
MOV
MOV
ADD
ADD
MOV
MOV
ECX, [mem1]
EDX, [mem2]
ECX, EAX
EDX, EBX
[mem1], ECX
[mem2], EDX
;
;
;
;
;
;
XLAT
linka
linka
linka
linka
linka
linka
u
v
u
v
u
v, všude perfektní párování => 3 cykly
; nepáruje, trvá 4 cykly
48
Procesorově závislé optimalizace kódu procesorů IA-32 architektury P5 a P6
MOV AL,[EBX+EAX]
; páruje v lince u i v, trvá 1 cyklus
MOVZX EAX, BYTE PTR [mem]
; nepáruje, trvá 4 cykly (3 na MMX)
XOR
MOV
; páruje v lince u i v, trvá 1 cyklus
; páruje v lince u i v, trvá 1 cyklus
EAX, EAX
AL, BYTE PTR [mem]
příklad 43 – rozdělení instrukcí do sekvence optimálnějších instrukcí
Určité komplexní instrukce lze nahradit sekvencí jiných (optimálnějších), které však
neprodukují zcela stejné výsledky. Na druhou stranu to v určitém malém lokálním kódovém
prostoru nemusí vadit, viz následující příklad.
JECXZ L
; nenastavuje EFLAGS, nepáruje, trvá 4-11 cyklů
TEST
JZ
ECX, ECX
L
; páruje v lince u i v, trvá 1 cyklus
; páruje v lince v, trvá 1/4/5/6 cyklů
LOOP
L
; nenastavuje EFLAGS, nepáruje, trvá 5-10 cyklů
DEC
JNZ
ECX
L
; páruje v lince u i v, trvá 1 cyklus
; páruje v lince v, trvá 1/4/5/6 cyklů
příklad 44 – rozdělení instrukce do instrukcí, které neprodukují identické výsledky
Při provedení výše uvedených změn se musí zkontrolovat, zda-li nenásledují za změněnou
instrukcí jiné instrukce, které čtou registr EFLAGS, v tom případě nelze optimalizaci provést.
5.4.2. Získání párovatelnosti instrukcí
Často lze docílit lepších výsledků tím, že se rozdělí instrukce původně nepárovatelné
na několik jiných, které však sami o sobě již párovatelné jsou. Ačkoli doba potřebná na vykonání
optimálnějších instrukcí je stejná nebo vyšší než doba potřebná na vykonání původní instrukce,
může tato optimalizace přesto produkovat (díky párovatelnosti nových instrukcí) efektivnější
kód. V tomto příkladu je vždy uveden blok komplexních instrukcí, za kterým následuje blok
instrukcí produkující stejné výsledky a který může být za určitých podmínek proveden
optimálněji.
PUSH
PUSH
[mem1]
[mem2]
; nepáruje, trvá 2 cykly
; nepáruje, trvá 2 cykly
MOV
MOV
PUSH
PUSH
EAX, [mem1]
EBX, [mem2]
EAX
EBX
;
;
;
;
CDQ
páruje
páruje
páruje
páruje
v
v
v
v
lince
lince
lince
lince
u
u
u
u
i
i
i
i
v,
v,
v,
v,
trvá
trvá
trvá
trvá
1
1
1
1
cyklus
cyklus
cyklus
cyklus
; nepáruje, trvá 2 cykly
MOV
SAR
EDX, EAX
EDX, 31
; páruje v lince u i v, trvá 1 cyklus
; páruje v lince u, trvá 1 cyklus
NOT
EAX
; nepáruje, trvá 1 cyklus
XOR
EAX, -1
; páruje v lince u i v, trvá 1 cyklus
NEG
EAX
; nepáruje, trvá 1 cyklus
XOR
INC
EAX, -1
EAX
; páruje v lince u i v, trvá 1 cyklus
; páruje v lince u i v, trvá 1 cyklus
příklad 45 – rozdělení instrukce do více instrukcí, které mohou být optimálnější
49
Procesorově závislé optimalizace kódu procesorů IA-32 architektury P5 a P6
5.4.3. Zmenšení počtu generovaných mikrooperací
Na architektuře P6 lze rozdělením instrukcí docílit menšího počtu celkově generovaných
mikrooperací, viz příklad.
LOOP
L
; 2 µopce v EU0, 1 µopce v EU1 a 8 µopcí v EU01
DEC
JNZ
ECX
L
; 1 µopce v EU01
; 1 µopce v EU1
POP
POP
POP
ECX
EBX
EAX
; 1 µopce v EU01, 1 µopce v EU2
; 1 µopce v EU01, 1 µopce v EU2
; 1 µopce v EU01, 1 µopce v EU2
MOV
MOV
MOV
ADD
ECX,
EBX,
EAX,
ESP,
[ESP]
[ESP+4]
[ESP+8]
12
;
;
;
;
1
1
1
1
µopce
µopce
µopce
µopce
v
v
v
v
EU2
EU2
EU2
EU01
příklad 46 – menší počet generovaných mikrooperací
Je zde však třeba podotknout, že menší počet mikrooperací neznamená automaticky
rychlejší vykonání kódu. Mikrooperace jsou vykonávány vždy jednou z pěti exekučních jednotek
a to pouze tehdy, pokud je daná jednotka volná, nejde vykonat dvě mikrooperace v jedné
jednotce během jednoho cyklu.
5.4.4. Shrnutí
Automatická detekce komplexních instrukcí je samozřejmě možná. Nahrazení nalezené
instrukce sekvencí jednodušších instrukcí tak, aby sekvence byla vykonána efektivněji než
původní kód, již tak samozřejmé není. Vzhledem k tomu, že vykonávaný kód má dynamický
charakter, kdy doba vykonání konkrétní instrukce je závislá na mnoha faktorech vyhodnocených
až v okamžiku vykonávání kódu (instrukce je/není ve vyrovnávací paměti, přistupovaná data
jsou/nejsou ve vyrovnávací paměti, vytíženost linky proudového zpracování, předchozí instrukce
atd.), není dotaz, zda-li má smysl optimalizovat komplexní instrukci výměnou za sekvenci jiných,
v obecném případě triviálně zodpověditelný. Optimalizace využívající nového registru jsou dále
komplikovány metodou nalezení vhodného volného registru, obecně nemusí být takový registr
k dispozici.
5.5.
Náhrada instrukcí
V některých případech lze zvýšit efektivnost kódu záměnnou jedné instrukce za jinou,
která je původní instrukci ekvivalentní.
5.5.1. Záměna ekvivalentních instrukcí
Některé instrukce jsou sobě ekvivalentní (či skoro ekvivalentní) a vhodnost jejich použití
závisí na konkrétním kontextu. Každá instrukce má určité vlastnosti, která vhodně splní daný
účel. Následující tabulka ukazuje některé (skoro) ekvivalentní instrukce, jež lze navzájem
zaměnit. Při záměně je třeba dávat pozor, aby nedošlo k nekompatibilitě obou instrukcí
v souvislosti s neúplnou ekvivalencí.
Pozn. V této kapitole se pod pojmem různá instrukce myslí i různé varianty jedné
instrukce.
50
Procesorově závislé optimalizace kódu procesorů IA-32 architektury P5 a P6
Instrukce
Vhodné vlastnosti
dlouhé kód., ruší závislosti mezi instrukcemi, nemění hod. příznaků
krátké kódování, označí registr jako prázdný, mění hodnoty příznaků
stejné jako XOR reg, reg
MOV
reg, 0
XOR
reg, reg
SUB
reg, reg
INC
reg/mem
ADD
reg/mem, 1
DEC
reg/mem
SUB
reg/mem, 1
CMP
reg, 0
TEST
reg, reg
AND
reg, reg
OR
reg, reg
MOV
reg1, reg2
LEA
reg1, [reg2]
krátké kódování, nemění hodnotu příznaku CF
dlouhé kódování, mění hodnotu příznaků OF, SF, ZF, AF, CF a PF
podobné jako INC
podobné jako ADD
dlouhé kódování
krátké kódování
podobné jako TEST
podobné jako TEST
MOVZX reg1, reg2
LEA
reg1, [reg2]
krátké kódování
dlouhé kódování
krátké kódování
dlouhé kódování
(imm <= 3), krátké kódování, páruje v lince u, mění hod. příznaků
LEA
reg, [reg*scl] (scl = 2^imm), dlouhé kódování, páruje v lince v, nemění příznaky
tab. 9 – (skoro) ekvivalentní instrukce
Občas nelze provést náhradu různých variant jedné instrukce obecně, ale lze ji uskutečnit
na základě aktuálního kontextu. Častým případem je změna operandů aritmeticko-logických
instrukcí, viz následující příklad, který je typickým binárním kódem vzniklým z překladače
vyššího jazyka.
SHL
reg, imm
1.
2.
MOV
...
ADD
EAX, [mem1]
EBX, [mem1]
; instrukce nemající vliv na EAX
příklad 47 – záměna ekvivalentních instrukcí
Druhou instrukci lze zaměnit s její variantou ADD EBX, EAX, a docílit tak kratšího
kódování instrukce, na procesorech bez datové vyrovnávací paměti59 se navíc zkrátí doba
potřebná k vykonání instrukce.
Rozpoznávání instrukcí, které lze zaměnit, a provedení této záměny lze automatizovat
poměrně snadno. Tato optimalizace je součástí programu SCOUT, viz odstavec „Algoritmus
optimalizace binárního kódu“ kapitoly A.2 dodatku B.
5.5.2. Lepší využitelnost exekučních jednotek
Procesory architektury P6 obsahují 5 exekučních jednotek. Každá mikrooperace se
provede na některé z těchto jednotek. Exekuční jednotky jsou specializovány, tj. každá jednotka
umí provést mikrooperace pouze určitého typu. Pokud jsou některé jednotky příliš vytíženy a jiné
59
procesory Intel 80386 a nižší
51
Procesorově závislé optimalizace kódu procesorů IA-32 architektury P5 a P6
mají volné zdroje (nejsou přítomny mikrooperace, které by daná jednotka mohla vykonat), dá se
zlepšit využití těchto jednotek náhradou některých instrukcí za jiné, viz následující příklad.
1.
2.
3.
4.
MOV
MOV
ADC
MOV
[mem1], EBX
EAX, [mem2]
EBX, ECX
ECX, EAX
;
;
;
;
(D0),
(D1),
(D0),
(D1),
2
1
2
1
µoperace
µoperace
µoperace
µoperace
provedené v EU3 a EU4
provedená v EU2
obě provedené v EU01
provedená v EU01
příklad 48 – provádění mikrooperací v exekučních jednotkách
První dvě instrukce budou dekódovány společně a i vykonány společně. Třetí a čtvrtá
instrukce budou také dekódovány společně, ale protože se skládají ze třech mikrooperací, které se
všechny mají vykonat v exekučních jednotkách číslo 0 nebo 160, vykonají se ve dvou hodinových
cyklech. Pokud se čtvrtá instrukce zamění za instrukci
4.
MOV
ECX, [mem2]
; (D1), 1 µoperace provedená v EU2,
která generuje mikrooperaci vykonávanou v exekuční jednotce číslo 2, ušetří se jeden
cyklus na vykonání instrukcí.
V daném okamžiku existuje několik variant jedné instrukce či několik různých instrukcí,
které jsou vzájemně kompatibilní a jejichž nahrazením lze docílit lepší propustnosti v exekuční
fázi.
Pro automatizaci výše uvedených optimalizací platí stejné závěry jako v kapitole 5.4.4.
5.6.
Odstranění redundantních instrukcí
Na první pohled by se mohlo zdát, že je zbytečné uvažovat o redundantních instrukcích,
protože není důvod, proč by překladač takovou instrukci do binárního kódu vkládal. V binárním
kódu se však opravdu vyskytují redundantní instrukce a jejich výskyt je poměrně častý.
Důvod je jednoduchý. Binární kód vzniká překladem z vyššího jazyka, který poskytuje
programátorovi různé jazykové konstrukty (cykly, přiřazení, rozskok, porovnání, návrat
z procedury apod.). Při překladu je každý konstrukt nahrazen jednou či více instrukcemi
strojového jazyka, překladač však nebere ohled na kontext konstruktu a pokud neprovede žádnou
další optimalizaci61, zůstanou v kódu redundantní instrukce. Následující příklad ukazuje úsek
kódu v jazyce C a způsob, jak byl přeložen do strojového kódu62.
Příkazy:
1. int i, j;
2. int main () {
3.
i = j;
4.
return j;
5. }
Instrukce:
1. PUSH
2. MOV
3. MOV
4. MOV
5. MOV
6. POP
7. RETN
EBP
EBP, ESP
EAX, [memi]
[memj], EAX
EAX, [memi]
EBP
; zbytečné
; zbytečné
; zbytečné
; zbytečné
příklad 49 – redundantní instrukce v binárním kódu
Na tomto krátkém příkladě lze vidět dvě základní zbytečnosti, kterých se překladač
dopustil. Redundantní instrukce číslo 1, 2 a 6 vznikly při vytváření rámce pro lokální proměnné
ve funkci, ačkoli funkce žádné lokální proměnné nepoužívá. Redundantní instrukci číslo 5
překladač vložil do kódu, protože si neuvědomil vzájemný kontext příkazů číslo 3 a 4 – používají
60
vykoná se v té exekuční jednotce, která bude volná dříve
a ze zkušenosti lze potvrdit, že se tak obvykle neděje
62
za použití překladače Microsoft Standard Compiler Verze 12.00.8168
61
52
Procesorově závislé optimalizace kódu procesorů IA-32 architektury P5 a P6
stejnou proměnnou. Vynecháním redundantní instrukce v optimalizovaném kódu se kód zkrátí a
většinou i zrychlí.
Automatizovaná detekce redundantních instrukcí a jejich vynechání v optimalizované
verzi kódu je možné, a je součástí programu SCOUT, viz odstavec „Algoritmus optimalizace
binárního kódu“ kapitoly A.2 dodatku B.
5.7.
Další techniky optimalizace
Některých optimalizací lze dosáhnout kombinací výše uvedených způsobů či jinými
technikami.
5.7.1. Eliminace prodlení při zápisu do částečného registru
Na architektuře P6 dojde k prodlení 5-6 hodinových cyklů, pokud po zápisu do
částečného registru následuje čtení z větší části stejného registru. Zdržení lze eliminovat
používáním instrukcí MOVZX a MOVSX a přístupem k celým registrům. Bohužel, toto řešení není
plně transparentní a tudíž všude použitelné, lze jej použít pouze tam, kde nejsou později čteny
zbylé bity přistupovaného registru. Například kód (viz příklad 30) lze nahradit kódem (viz
příklad 50), který netrpí výskytem prodlení.
Instrukce MOVZX a MOVSX jsou bohužel dost neefektivní
MOVZX EBX, BYTE PTR [mem1]
AND
EAX, 0FFFFFF00h
na architektuře P563 a proto se nabízí ještě další řešení –
OR
EBX, EAX
vynulování celého registru před zápisem do jeho části pomocí
MOV
EAX, EBX
instrukcí XOR či SUB. Registr je označen jako prázdný,
příklad 50 – eliminace
procesor ví, že horní bity zůstávají vynulovány a prodlení se
prodlení použitím instrukce
vyhne. Označení registru trvá až do doby, než se vyskytne
MOVZX
přerušení, dojde ke špatné predikci instrukce větvení či jiné
serializační instrukci. Tato technika však nefunguje, pokud k vynulování dojde instrukcí MOV
reg, 0, či se zapisuje do vyšších 8-bitových registrů (AH, BH, CH, ...), viz následující
příklad.
XOR
MOV
MOV
EAX, EAX
AL, 3
EBX, EAX
; SUB EAX, EAX má stejný efekt
XOR
MOV
MOV
AH, AH
AL, 3
BX, AX
; SUB AH, AH má stejný efekt
XOR
MOV
MOV
EAX, EAX
AH, 3
EBX, EAX
; SUB EAX, EAX má stejný efekt
MOV
MOV
MOV
EBX, 0
BL, DL
ECX, EBX
; není prodlení
; není prodlení
; není prodlení
; nastane prodlení
příklad 51 – eliminace prodlení použitím instrukce XOR nebo SUB
5.7.2. Eliminace závislostí mezi instrukcemi
Mezi instrukcemi stejného svazku nastane závislost, pokud pozdější instrukce čte hodnotu
operandu, kterou zapisuje dřívější instrukce ve svazku. Pozdější instrukce pak nemůže být
63
pokud by šlo o případ, kdy by kód měl být optimální na architekturách P5 i P6
53
Procesorově závislé optimalizace kódu procesorů IA-32 architektury P5 a P6
vykonána spekulativně dříve než dřívější instrukce, což zdržuje, zvláště pokud vykonání dřívější
instrukce je mnohem pomalejší než vykonání pozdější instrukce. Instrukce vložená mezi dřívější
a pozdější instrukci, a která zapisuje operand, ruší závislost.
Častým způsobem, jak zapsat do registru hodnotu 0 je 1.
DIV
EBX
MOV
[mem], EAX
použití instrukce XOR či SUB, bohužel, u těchto instrukcí 2.
XOR
EAX, EAX
procesor nerozpozná, že zapisují do registru a že ruší závislosti, 3.
4.
MOV
AL, CL
možnou optimalizací je nahrazení instrukce XOR (či SUB) 5.
ADD
EBX, EAX
instrukcí MOV, viz příklad 52.
příklad 52 – závislosti mezi
Zde nejsou instrukce číslo 3, 4 a 5 považovány za instrukcemi
nezávislé od předchozích instrukcí. Pokud se 3. instrukce
nahradí instrukcí MOV EAX, 0, závislost se přeruší64.
5.7.3. Použití instrukce LEA
Instrukce LEA může být použitelná v mnoha případech, protože
umožňuje posunout, sečíst a přemístit operand v jediném hodinovém
cyklu. Příklad vlevo, trvající čtyři hodinové cykly, lze nahradit jedinou
instrukcí LEA EAX, [EBX+8*ECX-1000], která se vykoná v jediném
příklad 53 – použití hodinovém cyklu.
instrukce LEA
Další možnosti použití instrukce LEA jsou uvedeny v tab. 9.
MOV
SHL
ADD
SUB
EAX,
EAX,
EAX,
EAX,
ECX
3
EBX
1000
64
a vznikne tím prodlení způsobené zápisem do částečného registru, lepší proto je ponechat v kódu obě
instrukce – MOV i XOR
54
Závěr
6. Závěr
Práce si kladla otázku, zda-li a za jakých podmínek je možná optimalizace binárního kódu
při použití pouze běžných prostředků nabízených procesory a formáty binárních souborů.
V první části práce jsou porovnány možnosti některých běžných procesorů, je popsán
obvyklý obsah binárních souborů běžných formátů a je uvedeno několik konstruktů vyšších
jazyků včetně způsobů jejich přeložení do strojového kódu. Dále jsou zde objasněny základní
způsoby optimalizace binárního kódu. Ve druhé části byla detailně popsána některá specifika
procesorů IA-32 a uvedeny podrobné možnosti optimalizací procesorů architektur P5 a P6.
Klíčovou otázkou celé problematiky, odpověď na ni je shodná s odpovědí na kladenou
otázku, je, jaké jsou možnosti analýzy binárního kódu, tedy – jaké jsou možnosti nalezení všech
kódových oblastí v binárním kódu a rozpoznání všech instrukcí větvení včetně jejich cílových
adres.
Bohužel, z důvodů, že běžné procesory disponují instrukcemi větvení s nepřímým
operandem, že formáty binárních souborů běžně neobsahují informaci o toku kódu a že i běžné
konstrukty vyšších jazyků jsou překládány takovým způsobem, kdy nelze zpětně rozpoznat
původní běh programu, není možno obecně dosáhnout kompletní analýzy binárního kódu a tudíž
není možné kód úplně optimalizovat. To se týká jak procesorů IA-32, tak ostatních popisovaných
procesorů. Výjimku tvoří JVM a jeho binární kód (Java Byte Code), který však není typický a
v diplomové práci byl uváděn jen pro srovnání.
Práce dále popisuje, jakými způsoby lze výše zmíněné „nepříjemné“ skutečnosti zmírnit
či obejít (rozpoznávání vyšších konstruktů, duplikace kódu, rozpoznávání počátků svazků
instrukcí, apod.) a dosáhnout tak alespoň částečné analýzy binárního kódu a tento kód
optimalizovat. Dlužno podotknout, že částečná analýza je však výsledkem vzniklým z nouze a
nelze ji pokládat za kladnou odpověď.
55
Literatura
7. Literatura
[AMD99]
[BRAN00]
[CADA91]
[CADA92]
[CIEM98]
[CIFU94]
[COMP98]
[CRVV92]
[DJGP96]
[FOG00]
[GJSB00]
[INTL94a]
[INTL94b]
[INTL95]
[INTL96]
[INTL97a]
[INTL97b]
[INTL99a]
[INTL99b]
[INTL99c]
Advanced Micro Devices, Inc. (1999): AMD-K6-III Processor Data Sheet.
http://www.amd.com.
Michal Brandejs (2000): Architektura počítačů (přednášky).
http://www.fi.muni.cz/usr/brandejs/AP/index.html.
RNDr. Ondřej Čada (1991): 80386 a 80387, příručka programátora.
PLUS s. r. o., Praha.
Ondřej Čada (1992): Mikroprocesor Motorola 68000, příručka programátora.
Grada a.s., Praha.
Cristina Cifuentes, Mike Van Emmerik (1998): Recovery of Jump Table Case
Statements from Binary Code. http://www.csee.uq.edu.au/~cristina/pubs.html.
Cristina Cifuentes (1994): Reverse Compilation Techniques.
http://www.csee.uq.edu.au/~cristina/pubs.html.
Compaq Computer Corporation (1998): Alpha Architecture Handbook.
http://www.support.compaq.com/alpha-tools/documentation/current/.
Petr Cápek, Juraj Rojko, Martin Vogel, Jiří Voves (1992): Turbo Assembler
3.0. Grada a.s., Praha.
DJ Delorie (1996): DJGPP COFF Spec.
http://www.delorie.com/djgpp/doc/coff/.
Agner Fog (2000): How to optimize for the Pentium family of
microprocessors. http://www.agner.org/assem/pentopt.zip.
James Gosling, Bill Joy, Guy Steele, Gilad Bracha (2000): The Java Language
Specification, Second Edition. http://java.sun.com/docs/books/jls/.
Intel Corporation (1994): Pentium™ Processor Family User's Manual Volume
1: Pentium™ Processor Family Data Book.
http://developer.intel.com/design/pentium/manuals/.
Intel Corporation (1994): Pentium™ Processor Family User's Manual Volume
3: Architecture and Programming Manual.
http://developer.intel.com/design/pentium/manuals/.
Intel Corporation (1995): 80C186EA/80C188EA Microprocessor User’s
Manual. http://www.intel.com.
Intel Corporation (1996): Intel386 EX™ Embedded Microprocessor User’s
Manual. http://www.intel.com.
Intel Corporation (1997): Embedded Intel486™ Processor Hardware
Reference Manual. http://www.intel.com.
Intel Corporation (1996, 1997): Intel Architecture Optimization Manual.
http://www.intel.com.
Intel Corporation (1999): Intel Architecture Software Developer’s Manual,
Volume 2: Instruction Set Reference.
http://developer.intel.com/design/PentiumIII/manuals/.
Intel Corporation (1999): IA-64 Application Developer’s Architecture Guide.
http://developer.intel.com/design/ia-64/manuals/.
Intel Corporation (1999): Intel Architecture Software Developer’s Manual,
Volume 3: System Programming.
http://developer.intel.com/design/PentiumIII/manuals/.
56
Literatura
[INTL99d]
[JIRO00]
[LIYE99]
[MOTO92]
[MSFT99]
[MUTH99]
[PIET94]
[PLAS91]
[SPRC00]
[SPRC92]
[SUN97]
[TISC]
[TISC95a]
[TISC95b]
[VAGN94]
Intel Corporation (1998, 1999): Intel® Architecture Optimization: Reference
Manual. http://www.intel.com.
Ing. Václav Jirovský, CSc. (2000): Principy počítačů. MATFYZPRESS,
Praha.
Tim Lindholm, Frank Yellin (1999): The JavaTM Virtual Machine
Specification, Second Edition. http://java.sun.com/docs/books/vmspec/2ndedition/html/VMSpecTOC.doc.html.
MOTOROLA INC. (1992): MOTOROLA M68000 FAMILY, Programmer’s
Reference Manual. http://e-www.motorola.com/webapp/DesignCenter.
Microsoft Corporation (1999): Microsoft Portable Executable and Common
Object File Format Specification.
http://msdn.microsoft.com/library/specs/msdn_pecoff.htm.
Robert Muth (1999): ALTO: A Platform For Object Code Modification.
http://www.cs.arizona.edu/alto/.
Matt Pietrek (1994): Peering Inside the PE: A Tour of the Win32 Portable
Executable File Format.
http://msdn.microsoft.com/library/techart/msdn_peeringpe.
doc. Ing. František Plášil, CSc. (1991): Operační systémy (skripta), České
vysoké učení technické.
SPARC International, Inc. (2000): The SPARC Architecture Manual,
Version 9. http://www.sparc.orq/standards.html.
SPARC International, Inc. (1992): The SPARC Architecture Manual,
Version 8. http://www.sparc.orq/standards.html.
Sun Microsystems (1997): UltraSPARC – IIi.
http://www.sun.com/microelectronics/manuals/.
Tool Interface Standards Committee: Portable Executable Formats (Version
1.1). http://x86.ddj.com/intel.doc/tools.htm.
Tool Interface Standards Committee (1995): Executable and Linking Format
(ELF) Specification (Version 1.2). http://x86.ddj.com/intel.doc/tools.htm.
Tool Interface Standards Committee (1995): Relocatable Object Module
Format (OMF) Specification (Version 1.1).
http://x86.ddj.com/intel.doc/tools.htm.
Ladislav Vágner (1994): SH help (elektronický manuál). Internet.
57
Dodatek A - Program SCOUT
Dodatek A - Program SCOUT
Na přiložené disketě je možno nalézt zdrojový i přeložený kód programu SCOUT (Simple
Binary Code Analysing and Optimizing Utility) pro OS Windows. Popis souborů a adresářů
na disketě je v textovém souboru README.TXT.
A.1. Uživatelská dokumentace
A.1.1.
Ovládání programu
Program SCOUT demonstruje možnosti jednoduché analýzy binárního kódu a provádí
jeho optimalizaci na základě výsledků předcházející analýzy.
Konfigurační soubor
Při spuštění programu SCOUT je třeba na příkazové řádce uvést jméno konfiguračního
souboru s parametry programu. Pokud je uveden text „-help“, vypíše se popis těchto parametrů,
viz následující tabulku.
Vzor
Popis
-i input_file
jméno vstupní spustitelného souboru
[-o output_file]
jméno výstupního optimalizovaného souboru
[-l [listing_file]] specifikace souboru pro uložení podrobných výsledků programu
[-e [on|off]]
nastavení výpisu aktuálně prováděných operací na standardní výstup
[-n [on|off]]
nastavení provedení analýzy programu
[-t [on|off]]
nastavení provedení optimalizace programu
[-p [on|off]]
nastavení automatického rozpoznávání neznámých svazků kódu
[-a address1 ...]
vložení externího seznamu počátků svazků kódu
tab. 10 – parametry programu SCOUT
Parametry programu mohou mít žádnou, jednu nebo více hodnot. Mezi dvěma parametry,
i mezi parametrem a hodnotou, musí být uveden jeden nebo více oddělovačů. Oddělovačem je
mezera, čárka, tabulátor, či konec řádky. Pokud je na řádce uveden znak „;“, je zbytek řádky
ignorován. Parametry jsou od hodnot rozlišeny počátečním znakem „-“, nezáleží na velikosti
písmen.
Popis parametrů programu
-i input_file
Jediný povinný parametr, za parametrem musí následovat jeho hodnota, která určuje
název vstupního souboru. Musí jít o 32-bitový spustitelný soubor OS Windows 95/98/NT/2000
formátu PEFF s koncovkou EXE či DLL a s jedinou kódovou sekcí1. Pokud je uvedena jiná
koncovka, či spustitelný soubor jiného formátu, je vypsáno chybové hlášení.
[-i output_file]
Je povinný pouze v případě, že je zapnuta optimalizace vstupního souboru.
Za parametrem následuje název výstupního souboru, do kterého se uloží optimalizovaná verze
vstupního souboru.
1
většina spustitelných programů pro OS Windows těmto podmínkám vyhovuje, optimalizovaná verze
vstupního programu však obsahuje dvě kódové sekce (viz odstavec „Duplikace kódu“ kapitoly 3.4.4) a
proto nemůže být vstupním souborem programu SCOUT
I
Dodatek A - Program SCOUT
[-l [listing_file]]
Zapíná generování podrobného výpisu výsledků práce programu. Pokud je uvedena
hodnota parametru, jsou výsledky zapsány do souboru s uvedeným názvem, jinak jsou výsledky
vypisovány na standardní výstup.
[-e [on|off]]
Zapíná či vypíná provádění popisu akcí, které program právě provádí. Implicitně je
provádění výpisu zapnuto, pokud je jako hodnota parametru uveden text „off“, je provádění
výpisu vypnuto. V ostatních případech je hodnota parametru ignorována.
[-n [on|off]]
Zapíná či vypíná provedení analýzy programu uloženého ve vstupním souboru. Implicitně
je analýza zapnuta, pokud je jako hodnota parametru uveden text „off“, je analýza vypnuta.
V ostatních případech je hodnota parametru ignorována. Při vypnuté analýze nelze provádět ani
optimalizaci binárního kódu, program pouze projde hlavičky souboru, hlavičky jeho sekcí a
některé datové struktury (relokace, importované a exportované soubory).
[-t [on|off]]
Zapíná či vypíná provedení optimalizace programu uloženého ve vstupním souboru.
Implicitně je optimalizace zapnuta, pokud je jako hodnota parametru uveden text „off“, je
optimalizace vypnuta. V ostatních případech je hodnota parametru ignorována.
[-p [on|off]]
Zapíná či vypíná automatické rozpoznávání svazků instrukcí, kterých nebylo dosaženo
během statické analýzy (viz alg. 1). Počátky svazků jsou identifikovány posloupností bytů
„0x55, 0x8B, 0xEC“2.
[-a address1 [address2 ...]]
Umožňuje uživateli vložit další vstupní body pro analýzu binárního kódu. Za parametrem
následuje jedna či několik hodnot, každá je virtuální adresou počátku svazku instrukcí.
A.1.2.
Činnost programu
Analýza vstupního souboru
Po spuštění programu jsou nejprve rozebrány parametry konfiguračního souboru. Je
načten vstupní soubor do paměti a proveden rozbor jeho hlaviček a struktur. Pokud je zapnuto
provedení analýzy binárního kódu, je inicializován seznam adres s počátky svazků, do kterého je
zapsána adresa vstupního bodu programu a případně i adresy všech exportovaných funkcí. Tento
seznam je třeba pro spuštění algoritmu statické analýzy, viz alg. 1. Výstupem algoritmu je
rozdělení kódové sekce do svazků několika typů. Kódové svazky obsahují instrukce, datové
svazky obsahují data, která byla čtena či zapisována některou analyzovanou instrukcí, ostatní
svazky obsahují datové struktury pro konstrukt switch, viz alg. 3, či jsou vyplněny jednou
hodnotou a u neznámých svazků se analýze nepodařilo rozhodnout, co obsahují. Po úspěšné
analýze jsou výsledky vypsány do specifikovaného souboru.
Optimalizace vstupního souboru
Pokud je zapnuto provedení optimalizace binárního kódu, program pokračuje v činnosti.
Jsou vytvářeny optimalizované kopie všech kódových svazků, viz odstavec „Duplikace kódu“
kapitoly 3.4.4. U každého svazku instrukcí původního kódu je rozhodnuto, zda-li se hodí
k optimalizaci (neobsahuje privilegované instrukce, instrukce pro práci se vstupně/výstupními
2
což je operační kód sekvence dvou instrukcí „PUSH EBP/MOV EBP,ESP“, tato posloupnost je obvykle
generována překladačem na začátku procedury
II
Dodatek A - Program SCOUT
zařízeními či FPU instrukce). Pokud se nehodí, je vytvořena zcela stejná kopie, pokud se hodí
k optimalizaci, je tento svazek optimalizován algoritmem optimalizace, viz alg. 2, který vytvoří
optimalizovanou kopii. Kopie svazků (identické i optimalizované) jsou vkládány sekvenčně
na nové adresy, veškeré cílové adresy instrukcí větvení jsou však směřovány na původní svazky
instrukcí, navíc jsou používány takové varianty instrukcí větvení, které používají 32-bitový
operand3 (i u identických kopií).
V druhém kroku optimalizace jsou přesměrovány přímé cílové adresy instrukcí větvení
(u IA-32 to jsou vždy relativní adresy) v sekci s optimalizovaným kódem tak, aby ukazovaly
na ekvivalentní optimalizované svazky instrukcí (ve stejné sekci).
Po skončení druhého kroku je třeba optimalizovanou část kódu setřást – na místech, kde
je rozdíl mezi adresou instrukce větvení a adresou cíle tohoto větvení natolik malý, že lze použít
instrukci s 8-bitovým či 16-bitovým operandem, je původní varianta zaměněna za kratší.
Setřesení je provedeno ve dvou cyklech přes všechny kopie svazků. V prvním cyklu jsou
setřeseny všechny instrukce v rámci jednoho svazku instrukcí a vzniklý rozdíl je doplněn
instrukcemi NOP, ve druhém kroku jsou jednotlivé svazky instrukcí posouvány a instrukce NOP
odstraněny, stále jsou však udržovány korektní hodnoty cílových adres přímých (relativních)
instrukcí větvení.
V posledním kroku jsou přesměrovány i cílové adresy přímých instrukcí větvení
původního kódu tak, aby ukazovaly na optimalizované kopie. Přesměrování nelze provést vždy,
prakticky jen v případě, že přímá instrukce větvení obsahuje 32-bitový operand.
Na závěr je nová kódová sekce přidána do souboru, hlavička této sekce je uložena
na volné místo na začátku souboru4 a výsledky o provedené činnosti jsou vypsány
do specifikovaného souboru.
A.1.3.
Výstupy programu
V případě nastavení korektních hodnot prvním třem parametrům (-i, -o, -l) a
ponechání implicitních hodnot ostatním parametrům program SCOUT provede analýzu a
optimalizaci vstupního souboru a zapíše výsledky do specifikovaných souborů. Během práce
programu je vypisována jeho činnost na standardní výstup. Podrobný popis výsledků lze rozdělit
do tří částí – rozbor hlaviček vstupního programu a jeho struktur, statická analýza programu a
jednoduchá optimalizace programu.
Hlavičky a struktury programu
První část výstupního souboru s výsledky zahrnuje výpis obsahu hlaviček binárního
souboru a hlaviček jeho jednotlivých sekcí, výpis adresáře (Data Directory) s umístěním struktur,
výpis počtu relokací, výpis seznamu importovaných funkcí a výpis seznamu exportovaných
funkcí. Některé struktury jsou nepovinné a nemusí být v souboru (a ani ve výpisu výsledků)
přítomny.
3
naštěstí je toto u procesoru IA-32 bez problémů dosažitelné, všechny optimální instrukce větvení mají
variantu s 32-bitovým operandem
4
díky tomu je možno prakticky nechat celý binární soubor nezměněný a na původních adresách, pokud
původní soubor neobsahuje volné místo pro hlavičku kódové sekce s optimalizovanou verzí, program
SCOUT nemůže tento soubor zoptimalizovat
III
Dodatek A - Program SCOUT
Analýza programu
Druhá část výsledků se týká analýzy binárního kódu. Obsahuje statistiku, do kolika
svazků byla rozdělena kódová sekce programu, kolik z těchto svazků bylo rozpoznáno jako
kódových, datových, ostatních a neznámých.
Po statistice následuje seznam instrukcí větvení, kde algoritmus statické analýzy nebyl
schopen rozpoznat jejich cílovou adresu. Dále je zde uveden podrobný výpis všech svazků
s obsahem jejich instrukcí či dat. Data jsou vypisována v hexadecimálním tvaru po bytech,
jedinou výjimku tvoří ta data, na jejichž adresu (kde jsou uložena) existuje položka v tabulce
relokací – zde jsou vypsána jako 4-bytové dlouhé slovo (Double Word).
Pro mnemoniku instrukcí je použit tzv. IDEAL mód používaný ve standardních
překladačích assembleru (např. Turbo Assembler firmy Borland), pokud je zjištěno volání
importované funkce je adresa jejího vstupního bodu nahrazena ve výpisu jejím jménem (a to i
v případě, že jde o nepřímé volání přes instrukci JMP5).
Optimalizace programu
Třetí část obsahuje výsledky podobného charakteru jako část analýzy. Navíc je zde
uveden seznam všech změn provedených v kódu během optimalizace. Nejsou vypisovány změny
hodnot operandů u instrukcích větvení s přímým operandem původních svazků.
A.2. Technická dokumentace
Program SCOUT je z větší části napsán v jazyce C++, zdrojové kódy implementující
disassembler celočíselných instrukcí byly převzaty z projektu T4 a jsou psány v jazyce C.
Rozšíření disassembleru na FPU instrukce je napsáno také v jazyce C.
Rozdělení programu
Implementace programu SCOUT je rozdělena do několika tříd, které tvoří dědičnou
hierarchii. Nejvýše je položena třída TCommandLine (commline.cpp), která obsahuje metody
k rozebrání konfiguračního souboru se vstupními parametry programu. Jejím potomkem je objekt
THeaders (headers.cpp), jejíž metody načtou vstupní soubor do paměti, zkontrolují jeho formát a
rozeberou jeho hlavičky, adresář a hlavičky jednotlivých sekcí. Další třídou v hierarchii je třída
TStructures (structs.cpp), která implementuje metody k rozboru některých struktur uložených
v souboru. Umožňuje provést rozbor relokací adres, seznamu importovaných funkcí a seznamu
exportovaných funkcí. Další třídou je TMemoryManager (manager.cpp), která implementuje
správce paměti. Metody této třídy umožňují provádět převody mezi virtuální a fyzickou adresou
původní (neoptimalizované) i nové optimalizované kódové sekce. Navíc je zohledněn fakt, že
sekce s optimalizovanou verzí nemusí být uložena v jediném souvislém bloku paměti.
Předposlední třídou v hierarchii je třída TAnalyser (analyse.cpp), která obsahuje metody
provádějící analýzu binárního kódu. Poslední třídou v hierarchii je třída TOptimizer
5
OS musí při inicializaci spouštěného programu projít všechna volání importovaných funkcí a
přesměrovat je na skutečně mapované adresy, to však může být poměrně dlouhotrvající operace, a proto
používají překladače trik, kdy je každé volání importované funkce přeloženo instrukcí CALL, která skočí
do svazku s instrukcí JMP a ta teprve zavolá importovanou funkci; přepočet je proveden jen u instrukce
JMP; některé programy ulehčují práci OS tím, že volání „známých“ (tj. systémových) importovaných
funkcí vůbec neoznačí jako volání importované funkce, ale vloží do kódu rovnou adresy, na kterých se
tyto systémové funkce obvykle vyskytují, tato „optimalizace“ však ztěžuje analýzu binárního kódu
(analyzátor nalezne instrukce větvení, které směřují mimo kódovou sekci programu)
IV
Dodatek A - Program SCOUT
(optimize.cpp a tools.cpp) s metodami pro optimalizaci kódu. Vytváření konkrétních variant
některých instrukcí a popis jejich vlastností je realizováno třídou TInstruction (instr.cpp) a
jejími potomky. Operandy instrukcí a metody pro jejich porovnávání jsou implementovány
ve třídě TOperand (operand.cpp). Třída TBundle (bundle.cpp) implementuje svazek instrukcí.
Při implementaci algoritmů byla často využívána struktura spojového seznamu TList a
setříděného spojového seznamu TSortedList (obojí v list.cpp). Výpisy na standardní výstup i
do souboru s výsledky je kontrolován třídou TDisplay (display.cpp).
Funkce disassembleru CPU i FPU instrukcí jsou uloženy v souboru disasm.c. K popisu
instrukcí je použito struktur definovaných v souboru intel.c, který byl automaticky vygenerován
dalšími pomocnými nástroji.
Algoritmus statické analýzy
Algoritmus statické analýzy (viz kapitolu 3.4.2) je realizován metodou ProcessPointers
ve třídě TAnalyser.
Algoritmus rozpoznání konstruktu switch
Nepřímé instrukce větvení značně ztěžují analýzu binárního kódu, protože v mnoha
případech nelze statickou analýzou zjistit cílovou adresu větvení. Jednou z mála výjimek je
instrukce JMP používající jako cílovou adresu součet indexového registru násobeného čtyřmi
a konstantního posunutí (JMP – např. JMP [DWORD DS:EDX*4+004062EA]). Tato instrukce
obvykle značí přítomnost kódu vzniklého přeložením konstruktu switch z vyššího jazyka, jehož
hodnoty jednotlivých větví jsou dostatečně blízko u sebe. Indexový registr představuje
rozskokovou proměnnou (násobenou 4, protože velikost ukazatele na IA-32 jsou 4 byty) a
konstantní posunutí je adresou tabulky s cílovými adresami jednotlivých větví.
Pouhá přítomnost instrukce (JMP) je však nedostatečná, pro rozpoznání konstruktu switch
je třeba analyzovat další instrukce, které se objevují v blízkosti před ní (viz odstavec „Konstrukt
switch“ kapitoly 3.4.2). Typicky to jsou tři následující instrukce. První upraví hodnotu
rozskokové proměnné tak, aby indexovala od nuly (SUB – např. SUB EDX,00000002). Druhá
instrukce zkontroluje, zda-li hodnota rozskokové proměnné patří do intervalu, kterým se indexuje
tabulka adres s cílovými adresami (CMP – např. CMP EDX,00000003). Třetí provede odskok
na větev „default“ či za konstrukt switch, pokud je hodnota rozskokové proměnné mimo
testovaný interval (JA – např. JA 00401047), viz příklad 2.
Výše uvedené instrukce bývají navíc často proloženy instrukcemi MOV (MOV), které
kopírují hodnoty rozskokové proměnné do více operandů, místo původního operandu s hodnotou
rozskokové proměnné se tak ve výše uvedených instrukcích může objevit operand s kopií této
hodnoty.
Dalším častým jevem přítomným v kódu je překlad hodnoty rozskokové proměnné. Děje
se tak v případě, že hodnoty výrazu, podle kterého konstrukt switch větví, netvoří souvislý blok.
V tom případě bývá mezi instrukcemi (JA) a (JMP) další instrukce, která druhý překlad provede
(MOVZX – např. MOVZX EDX,[BYTE DS:EDX+00406306]). Konstantní posunutí v instrukci
(MOVZX) označuje místo, kde je uložen obsah překladové tabulky – pole 8-bitových hodnot.
Algoritmus implementovaný v metodě RecognizeSwitchStatement třídy TAnalyser
využívá uvedených znalostí. Na každý svazek instrukcí, který je ukončen nepřímou instrukcí
větvení (JMP) se aplikuje tento algoritmus. Algoritmus postupuje po instrukcích od vyšších adres
k nižším a kontroluje, zda-li jsou přítomny (v tomto pořadí) instrukce (JMP) (1. podmínka), (JA)
V
Dodatek A - Program SCOUT
(2. podmínka) a (CMP) (3. podmínka)6. Dále si poznamená, zda-li je mezi instrukcemi (JMP) a
(JA) přítomna instrukce (MOVZX) (skutečnost A – případ s překladem). Navíc sleduje
přítomnost instrukcí (MOV) a vytváří si seznam operandů se stejnými hodnotami, instrukce
(CMP), (JA), (JMP) a (MOV) musí pracovat s některým operandem tohoto seznamu (další
podmínky). Pokud algoritmus narazí na instrukci (CMP) při splnění všech podmínek, označí
instrukce (CMP), (JA) a (JMP) za idiom a stanoví, že na konstantní adrese uvedené v instrukci
(JMP) leží tabulka cílových adres větvení konstruktu switch o velikosti určenou instrukcí (CMP).
Pokud je platná (skutečnost A), je chování algoritmu pozměněno následovně. Instrukce
(CMP) a (JA) musí pracovat s operandy, jež obsahují indexový registr zdrojového operandu
instrukce (MOVZX) nebo jeho kopii (uloženou v seznamu) a instrukce (JMP) pracuje s cílovým
operandem instrukce (MOVZX). Algoritmus označí konstantní posunutí uvedené v instrukci
(MOVZX) za adresu překladové tabulky, její velikost je určena instrukcí (CMP), a velikost
tabulky cílových adres větvení je určena rozdílem nejvyšší a nejnižší hodnoty (typicky 0)
v překladové tabulce.
1)
2)
3)
4)
6
Inicializuj I na poslední instrukci ve svazku
Inicializuj C na 0 (číslo poslední splněné podmínky)
Dokud I je platná instrukce opakuj body 4 a 5
Dle hodnoty C větvi:
a) větev „C rovno 0“ (žádná podmínka není dosud splněna)
i) pokud je I instrukce JMP, jejíž operand je vzoru
[IDX_REG*4+KONST_POSUNUTI], tak
- inicializuj R na IDX_REG (poznamenej si indexový registr)
- inicializuj seznam L, obsahuje registr R (kopie hodnot
registru R)
- inicializuj D0 na KONST_POSUNUTI (tabulka cílových adres)
- inicializuj D1 na neplatnou hodnotu (překladová tabulka)
- inkrementuj C (první podmínka splněna)
- skonči větev
ii) jinak skonči algoritmus – konstrukt switch nebyl rozpoznán
b) větev „C rovno 1“ (první podmínka splněna)
i) pokud je I instrukce JA, tak
- inkrementuj C (druhá podmínka splněna)
- skonči větev
ii) pokud je I instrukce MOVZX, jejíž cílový operand je v seznamu
L a zdrojový operand je vzoru [IDX_REG+KONST_POSUNUTI], tak
- reinicializuj R na IDX_REG (poznamenej si indexový registr)
- reinicializuj seznam L, obsahuje registr R (kopie hodnot
registru R)
- inicializuj D1 na KONST_POSUNUTI (překladová tabulka)
- skonči větev
iii) pokud je I instrukce MOV, jejíž cílový či zdrojový operand je
v seznamu L, tak
- přidej i druhý operand (který není v seznamu L) do seznamu L
- skonči větev
c) větev „C rovno 2“ (druhá podmínka splněna)
i) pokud je I instrukce CMP, jejíž cílový operand je v seznamu L,
tak
- inicializuj S na hodnotu zdrojového operandu instrukce CMP
- skonči algoritmus, konstrukt switch byl rozpoznán, výstupem
jsou hodnoty D0, D1 a S
přítomnost instrukce (SUB) se nekontroluje, počátek intervalu možných hodnot může začínat na 0
VI
Dodatek A - Program SCOUT
5) I je instrukce stejného svazku, která předchází I
6) Skonči algoritmus, konstrukt switch nebyl rozpoznán
alg. 3 – rozpoznání konstruktu switch
Výstupní hodnoty algoritmu při rozpoznání konstruktu switch jsou interpretovány takto:
Pokud D1 obsahuje platnou hodnotu, je S velikost překladové tabulky ležící na adrese D1. Rozdíl
nejvyšší a nejmenší hodnoty v překladové tabulce udává velikost tabulky cílových adres ležící
na adrese D0. Pokud D1 neobsahuje platnou hodnotu, je S velikost tabulky cílových adres,
překladová tabulka není použita.
Pozn. 1: Algoritmus nepopisuje stav, kdy jsou mezi instrukcemi idiomu přítomny další
instrukce, které mění hodnotu rozskokové proměnné. V tom případě jde zřejmě o nestandardní
způsob přeložení konstruktu switch a algoritmus by měl skončit s negativním výsledkem.
Pozn. 2: Rozpoznání překladové tabulky nemusí být indikováno přítomností instrukce
(MOVZ),
často
je
použita
sekvence
dvou
instrukcí
XOR REG32,REG32
/
7
MOV REG8,[BYTE DS:REG0+KONST_POSUNUTI] , kde REG0 je porovnávanou hodnotou v instrukci
(CMP) a REG32 indexuje tabulku cílových adres v instrukci (JMP). Program SCOUT rozpoznává
i tento případ.
Algoritmus optimalizace binárního kódu
Algoritmy k vytvoření optimalizované verze svazku, k odstranění redundantních instrukcí
MOV a k výměně operandu za optimálnější (viz kapitolu 3.5) jsou v metodách CreateOptimized a
RemoveRedundantMoves třídy TOptimizer.
Algoritmy k nastavení cílových adres instrukcí větvení
Algoritmus duplikace kódu vytvoří kopie původních svazků instrukcí, čímž eliminuje
problém optimalizace neúplně analyzovaného kódu. Použití této techniky však vyžaduje řešení
souvisejících problémů. Velikosti optimalizovaných verzí svazků mohou být obecně jiné,
než velikosti původních svazků. U instrukcí větvení využívajících relativní operandy se musejí
tyto operandy přepočítat. Navíc, v případě použití 8-bitových (či 16-bitových) operandů
u instrukcí větvení, je nutno počítat se situací, že se přepočítané hodnoty nevejdou do 8-bitového
(16-bitového) rozsahu. Další operací, kterou si duplikace kódu vyžaduje, je přesměrování
cílových adres u rozpoznaných instrukcí větvení původního kódu na vytvořené kopie
odkazovaných svazků. Následující čtyři algoritmy řeší všechny výše uvedené problematiky.
1) Pro všechny analyzované svazky instrukcí B opakuj body 2 až 4
2) Založ prázdný svazek instrukcí Bopt
3) Pro všechny instrukce I ve svazku B proveď body a až c
a) pokud je I instrukce větvení svazku B, která používá relativní
operand O ukazující na původní svazek B2, tak
i) vytvoř optimalizovanou variantu Iopt ve verzi s 32-bitovým
operandem O32, který ukazuje na svazek B2
b) jinak vytvoř ekvivalentní optimalizovanou variantu Iopt dle alg. 2
c) Iopt vlož do Bopt ve stejném pořadí
4) Bopt umísti do sekce s duplikovaným kódem
alg. 4 – optimalizace svazků instrukcí a expanze operandů u instrukcí větvení
Výše uvedený algoritmus je implementován v metodách ExpandInstruction,
ExpandOperand a CreateOptimized třídy TOptimizer.
7
REG8 je spodních 8-bitů registru REG32, překladová tabulka typicky obsahuje 8-bitové hodnoty
VII
Dodatek A - Program SCOUT
1) Pro všechny optimalizované kopie svazků Bopt opakuj bod 2
2) Pro všechny instrukce svazku Bopt proveď body a až d
a) pokud je I instrukce větvení svazku Bopt, která používá relativní
operand ukazující na původní svazek B2, přepočítej operand tak,
aby směřoval na optimalizovanou kopii svazku B2
b) pokud se provedeným přepočtem zmenší relativní hodnota operandu
instrukce I tak, že ji lze zakódovat 8 bity a pokud k instrukci I
existuje varianta I8 používající 8-bitový operand, nahraď
instrukci I variantou I8
c) pokud došlo ke zkrácení instrukce I, posuň všechny následující
instrukce ve svazku
d) pokud došlo ke zkrácení svazku Bopt, doplň jej instrukcemi NOP,
aby zůstala zachována jeho velikost a poznamenej si do položky
A[Bopt] přiřazené svazku Bopt velikost ušetřeného prostoru (resp.
součet velikostí všech předchozích ušetřených instrukcí)
alg. 5 – přesměrování a kontrakce relativních cílových adres instrukcí větvení
Výše uvedený algoritmus je implementován v metodách CompactOperand,
CompactBundle, ShiftOperand a ShiftOperands třídy TOptimizer.
1) Pro všechny optimalizované kopie svazků Bopt opakuj body 2 a 3
2) Dle obsahu položky A[Bopt] posuň umístění svazku Bopt o ušetřený
prostor
3) Pro všechny instrukce ve svazku Bopt proveď bod a
a) Pokud je I instrukce větvení s relativním operandem O svazku Bopt
ukazující na svazek B2opt, přepočítej O tak, aby ukazoval
na svazek B2opt se zohledněním fyzického setřesení obou svazků,
využij k tomu položky A[Bopt] a A[B2opt] (v této fázi kód pouze
kontrahuje, případné 8-bitové relativní hodnoty operandů zůstanou
vždy v 8-bitovém rozsahu)
alg. 6 – setřesení optimalizovaných kopií svazků instrukcí
Výše uvedený algoritmus je implementován v metodách
CompactOperands třídy TOptimizer.
CompactCode
a
1) Pro všechny analyzované svazky B opakuj bod 2
2) Pro všechny instrukce ve svazku B proveď bod a
a) Pokud je I instrukce větvení svazku B s relativním operandem O
dostatečného rozsahu (typicky 32-bitů) ukazujícím na svazek B2,
přepočítej O tak, aby ukazoval na optimalizovanou kopii svazku B2
alg. 7 – přesměrování cílových adres instrukcí větvení původních svazků
Výše uvedený algoritmus je implementován v metodě ChangeBundlePointers třídy
TOptimizer.
Pozn.: Prakticky všechny instrukce větvení procesorů IA-32 disponují s variantou, kde
vystupuje 32-bitový relativní operand. Jedinými výjimkami jsou instrukce LOOP, LOOPE, LOOPNE a
JECXZ, které existují pouze ve variantě s 8-bitovým operandem. Instrukce LOOP, LOOPE a LOOPNE
se však ve 32-bitových binárních souborech obvykle nevyskytují8. Instrukce JECXZ je používána
jen zřídka, navíc lze nahradit skoro-ekvivalentní posloupností TEST ECX,ECX / JZ, která je
na architektuře P5 optimálnější než instrukce JECXZ. Záměna je součástí optimalizačního
algoritmu, který musí sledovat, zda-li nedojde k porušení funkčnosti programu9.
8
autoři architektur procesorů IA-32 se zřejmě rozhodli tyto instrukce více nepodporovat; jejich použití je
na všech procesorech IA-32 mnohem pomalejší než ekvivalentní posloupnosti jednodušších instrukcí
9
rozdíl mezi instrukcí JECXZ a její skoro-ekvivalentní posloupností je, že instrukce JECXZ nenastavuje
příznaky, tento rozdíl má však dopad jen v minimu případů
VIII
Dodatek A - Program SCOUT
A.3. Měření výsledků programu SCOUT
Objektivní měření programem SCOUT dosažených výsledků je obtížné z několika
důvodů. Především je nutné vzít v úvahu závěry diplomové práce – výsledky programu SCOUT
jsou vždy jen částečné. Dále je třeba reflektovat, že účelem programu SCOUT je „pouze“
demonstrovat použitelnost některých přístupů k analýze a optimalizaci binárních souborů
popisovaných v této práci, většina zde popsaných algoritmů je implementována jen v té
nejjednodušší variantě, k praktickému využití by bylo třeba vyřešit mnoho dalších otázek.
Výsledky analýzy
Výsledkem analýzy binárního kódu je rozpoznání oblastí se spustitelným kódem a
instrukcí větvení včetně jejich cílových adres. Vzhledem k tomu, že nelze přesně zkontrolovat,
kolik instrukcí větvení binární kód obsahuje (pokud neproběhne úplná analýza binárního kódu),
není tento výsledek měřen. Měřit poměr součtu velikostí rozpoznaných kódových oblastí
ku součtu velikostí všech kódových oblastí nelze opět přesně zajistit, protože nejsou obecně
všechny oblasti se spustitelným kódem známy. Proto je poměr počítán součtem velikostí všech
rozpoznaných oblastí (tj. velikost rozpoznaných kódových, datových a ostatních svazků, viz
odstavec „Analýza programu“ kapitoly A.1.3) ku velikosti celé kódové sekce. Následující
tabulka ukazuje výsledky analýzy některých vybraných programů.
Soubor
Kód
Data
Ostatní
Nerozpoznané
Poměr
exchange.exe
16598
0
102
3056
84,53%
array.exe
16857
0
91
3048
84,76%
wininet.dll
249433
304
781
131026
65,66%
sqlsrv32.dll
258673
0
16070
85427
76,28%
tab. 11 – výsledky analýzy programu SCOUT u vybraných příkladů
Sloupce Kód, Data a Ostatní obsahují součet velikostí všech rozpoznaných svazků, které
byly zařazeny do dané kategorie. Sloupec Nerozpoznané obsahuje velikost nerozpoznaných dat
v kódové sekci souboru. Sloupec Poměr obsahuje poměr velikosti rozpoznaných dat ku velikosti
celé kódové sekce.
První dva programy, vybrané k měření jsou součástí měření výsledků optimalizace (viz
následující odstavec), byly přeloženy překladačem Microsoft Standard Compiler Verze
12.00.8168. Testované sdílené knihovny byly zvoleny z instalace OS Windows, byly vybrány
knihovny s větším počtem exportovaných funkcí.
Výsledky optimalizace
Úspěšným výsledkem optimalizace binárního kódu je zkrácení jeho velikosti či zkrácení
doby nutné k jeho provedení. Vzhledem k použité technice duplikace kódu (viz odstavec
„Duplikace kódu“ kapitoly 3.4.4) nemá smysl měřit výsledky optimalizací, které zkracují
spustitelný kód (velikost optimalizovaného kódu je vždy větší než velikost původního kódu).
Měřené výsledky rychlostních optimalizací programu SCOUT jsou velmi zkreslené
u programů, které vyžadují vstup od uživatele, či používají vstupně/výstupní zařízení. Navíc je
velikost dosaženého zrychlení natolik minimální, že je nelze u běžných programů prakticky
postřehnout. Z těchto důvodů bylo přistoupeno ke specifickému způsobu měření, jehož účelem je
spíš zjistit, zda-li došlo k nějaké změně v rychlosti spouštěné úlohy, než přesně měřit, o jak
velkou změnu se jedná.
Pro účely měření byly vytvořeny dva speciální příklady v jazyce C, které provedly
jednoduchou operaci. Prováděná operace byla předmětem měření výsledků optimalizace.
IX
Dodatek A - Program SCOUT
K dosažení postřehnutelného rozdílu mezi rychlostí původního a optimalizovaného kódu
s testovanou operací byla operace uzavřena do cyklu a mnohonásobně opakována.
Pro zamezení vlivu dalších činitelů (prostředí multiprocesového OS, práce s diskem
apod.) byl čas měřen voláním systémových funkcí přímo v testovaném programu. Změření času
bylo provedeno vždy těsně před vykonáním mnohonásobného cyklu s testovanou operací a těsně
po jeho dokončení. Jediným výstupem testovaného programu byl rozdíl obou časů. Každý
program byl navíc spuštěn desetkrát za sebou, při spouštění se vždy střídala původní a
optimalizovaná verze programu.
Prvním zkušební operací bylo nastavení hodnot dvěma celočíselným proměnným a jejich
následné prohození přes třetí proměnnou, obsah proměnných byl pak zobrazen funkcí printf.
Prohození bylo iterováno 100,000,000 krát.
Druhým testovaným příkladem bylo vyplnění pole o velikosti 1000 celočíselných položek
sekvencí náhodných čísel (nepočítáno do měření času) a spočtení průměru, minima a maxima
z těchto hodnot. Počítání bylo opakováno 100,000 krát.
Pozn. Vykonání funkce printf pro zobrazení obsahu proměnných je natolik zdržujícím
faktorem, že bylo při testu z příkladu odstraněno.
Následující tabulka ukazuje naměřené hodnoty obou příkladů.
Měření
1. původní (ms)
1. optimal. (ms)
2. původní (ms)
2. optimal. (ms)
1.
4280
4010
6420
6210
2.
4280
3950
6310
6100
3.
4230
4010
6530
6260
4.
4290
4070
6320
6310
5.
4280
3900
6370
6370
6.
4180
4060
6530
6200
7.
4180
4060
6310
6200
8.
4230
4010
6540
5870
9.
4060
3960
6430
6260
10.
4230
4120
6480
6370
Průměr
4224
4015
6424
6215
tab. 12 – výsledky optimalizace programu SCOUT u vybraných příkladů
Rozdíl časů průměru trvání původního a optimalizovaného kódu prvního příkladu je
209 ms, což činí zrychlení o 4,95%. U druhého příkladu tvoří rozdíl 209 ms, což je zrychlení
o 3,25%. Měření bylo provedeno na počítači s procesorem Intel Celeron pracující na frekvenci
433 MHz, s datovou i kódovou vyrovnávací pamětí první úrovně o velikosti 16kB a 64 MB RAM
hlavní paměti.
X
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci
Informace potřebné k napsání této části práce byly převzaty především z originálních
dokumentací firmy Intel [INTL95], [INTL96], [INTL97a], [INTL94a], [INTL94b], [INTL99c],
[INTL99a] a dále doplněny mnoha jinými cennými zdroji, především [FOG00], [CADA91],
[CRVV92], [BRAN00], [VAGN94]. K použitému názvosloví a k pochopení některých
konstrukcí pomohly [JIRO00] a [PLAS91].
B.1. Historie a vývoj procesorů Intel
Dosud na trh uvedené 32-bitové typy procesorů rodiny IA-32, jmenovitě Intel 80386
(1985), Intel 80486 (1989), Intel Pentium (1993), Intel Pentium MMX, Intel Pentium Pro (1995),
Intel Pentium II (1997), Intel Pentium III (1999) a Intel Pentium IV (2001) jsou následníky svých
starších 16-bitových10 předchůdců Intel 8086 (1978), Intel 80186 a Intel 80286 (1982) a ačkoli se
podstatně liší svou vnitřní stavbou, jsou s nimi programově kompatibilní11.
Pro úplnost je třeba dodat, že i procesor Intel 8086 měl předchůdce (už ne binárně
kompatibilní), z kterých vycházel – procesory Intel 8080/8085 (1973/1977) a Intel 4004 (1969) .
Z důvodu nešikovně navržené stavby již prvních verzí procesoru byl procesor Intel
rozšiřován vždy jen za cenu dalších těžkopádných řešení, typicky přidáním nového pracovního
režimu. Tento postup však vyžadoval definovat rozhraní pro předávání dat a způsob bezpečného
přepínání mezi režimy a řešení řady dalších otázek, čímž vznikla obrovská a nepřehledná
struktura se spoustou výjimek a nejasností.
Původně měl procesor jediný pracovní režim, procesory Intel 80286 a vyšší již obsahují
dva pracovní režimy, které se nazývají reálný a chráněný režim (Real, Protected Mode). Reálný
režim je zpětně kompatibilní s původním jediným režimem. Je určen pro jednoprocesové
prostředí, není zde implementována ochrana paměti, ani její virtualizace. Adresování reálného
režimu je založeno na rozdělení paměti do stejně velkých úseků, které se vzájemně překrývají,
pro označení libovolného místa paměti jsou používány dvě hodnoty – 16-bitový segment a ofset.
20-bitová adresová sběrnice dokáže obsáhnout paměťový prostor o maximální velikosti pouze
1MB12. Tato situace nebyla dlouho udržitelná a dalšími procesory se velikost adresové sběrnice
zvětšila nejdříve na 24 (Intel 80286) a později na 32 bitů (Intel 80386). Její plné využití však
mohlo přijít až s příchodem chráněného pracovního režimu.
Chráněný režim dostal svůj název podle schopnosti ochrany paměti i přístupu k perifériím
(zavádí čtyři úrovně ochrany). Dále umožňuje virtualizaci paměti stránkováním (od procesoru
Intel 80386) i segmentací a podporuje i spouštění více úloh (multitasking). Nutno podotknout, že
z uvedených nových vlastností se prakticky plně používá pouze možnost virtualizace paměti a
její ochrany pomocí stránkování – ze čtyř úrovní ochrany se používají pouze dvě úrovně,
10
existují i varianty s 8-bitovou datovou sběrnicí, procesory Intel 8088 (1979) a Intel 80188
i když určité vlastnosti byly v průběhu vývoje procesoru změněny, tyto změny však nejsou veliké a
obvykle nemají vliv na chod programu; většinou se jednalo o nápravu chyb v návrhu procesoru
12
s příchodem procesoru Intel 80286, který měl 24-bitovou adresovou sběrnici, je možno v reálném
režimu vyadresovat o 65520 bytů paměti více, tzv. paměť HMA (High Memory Area)
11
XI
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci
multitasking je řešen programově v závislosti na operačním systému a segmentace není
využívána vůbec13.
Příchodem procesoru Intel 80386 se, z hlediska jeho programového použití, procesor Intel
již moc neliší, v praxi je to pouze zavedení nových instrukcí. Zato vnitřní stavba procesoru se
mění prakticky s příchodem každého nového typu.
Dále je popsán poměrně podrobně procesor Intel 8086, z kterého vycházejí všechny
následující procesory, u každého dalšího typu jsou napsány hlavní vlastnosti, které daný model
přinesl, či kterými se odlišuje od svého předchůdce.
B.2. Procesor Intel 8086
Procesor Intel 8086 obsahuje jediný mód práce. Vycházel z koncepce CISC, nabízí tedy
všestranné a komplexní instrukce. Obsahuje 16-bitovou datovou a 20-bitovou adresovou sběrnici.
Při kódování instrukcí i dat jsou nižší bity uloženy na nižších adresách (Little Endian).
Paměť
20-bitovou adresovou sběrnicí je možno vyadresovat 1MB paměťového prostoru. Každá
fyzická adresa paměti je určena dvojicí 16-bitových hodnot – segment:ofset, kde segment je
paměť o maximální velikosti 65536 bytů, každý následující segment pak začíná na adrese o 16
bytů výše (segmenty se překrývají). Ofset je posunutím v daném segmentu. Tato (ne asi úplně
nejšťastnější) koncepce vede k různým paradoxům, například dvě různé dvojice segment:ofset
mohou ukazovat na jednu fyzickou adresu.
Jsou zavedeny dva termíny pro adresu v paměti. Zatímco program pracuje se segmentem
a s 16-bitovou efektivní adresou, jsou data uložena na fyzické adrese v rozsahu 20 bitů,
následující obrázek ilustruje přepočet hodnoty segmentu a efektivní adresy na fyzickou adresu.
19
4
3
0
16-bitová hodnota segmentu
19
0
19
16
0
0
0
15
0
+
0
0
0
0
16-bitová efektivní adresa
=
0
20-bitová fyzická adresa
obrázek 5 – přepočet efektivní adresy na fyzickou
Adresace paměti může být buď přímá (uvedení konstanty) či nepřímá. Nepřímá adresa je
buď hodnota registru, či vznikne součtem dvou registrů, či registru a konstanty, či dvou registrů i
13
nevyužívání těchto vlastností je možná způsobeno neexistencí podobných rysů na jiných procesorech,
tvůrci rozšířených operačních systémů (Linux/Unix, Windows NT), které nejsou spjaty s jednou HW
platformou, se drží běžných vlastností architektury procesoru a nemají důvod tyto nestandardní vlastnosti
podporovat
XII
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci
konstanty dohromady. Registry se označují jako bázový (BX či BP) a indexový (SI či DI) a
pro adresaci jsou povoleny jen kombinace registrů uvedené v tabulce.
BX+SI
BX+DI
BP+SI
BP+DI
SI
DI
16-bitová konstanta
BX
BX+SI+konstanta (16
BX+DI+konstanta (16
BP+SI+konstanta (16
BP+DI+konstanta (16
SI+konstanta (16 či
DI+konstanta (16 či
BP+konstanta (16 či
BX+konstanta (16 či
či 8 bitů)
či 8 bitů)
či 8 bitů)
či 8 bitů)
8 bitů)
8 bitů)
8 bitů)
8 bitů)
tab. 13 – možnosti adresace procesoru Intel 8086
Registry
Procesor obsahuje čtyři 16-bitové registry (AX, BX, CX, DX), které vystupují jako operandy
v aritmeticko-logických (ale i jiných) instrukcích. Přestože se dají spolu většinou navzájem
zaměňovat, má každý z registrů svůj specifický význam, který je rozlišen existencí jen určitých
variant instrukcí, rychlostí daných variant či délkou kódování daných variant. Speciální význam
registrů je následující:
AX – střadač (Acumulator), určen jako cílový operand aritmeticko-logických instrukcí či
instrukcí přesunu dat, při použití tohoto registru je obvykle kódování i doba vykonání instrukce
kratší.
BX – báze (Base), bázový registr při nepřímé adresaci paměti.
CX – čítač (Counter), implicitní čítač cyklů či pro práci s bloky dat.
DX – data (Data), operace vstupu a výstupu.
Tyto registry lze dělit na osm 8-bitových registrů AL (Low, nižší), AH (High, vyšší), BL,
BH, CL, CH, DL a DH, jak je uvedeno na obrázku.
15
8
7
AX
AH
0
AL
BX
BH
0
FLAGS
IP
Obecné registry
Řídící registry
BL
CX
CH
CL
DX
DH
15
DL
15
15
0
0
CS
SS
DS
ES
SI
DI
SP
BP
Segmentové
registry
obrázek 6 – registry procesoru Intel 8086
Dále procesor obsahuje dva 16-bitové indexové registry SI (Source Index) a DI
(Destination Index) používané k indexaci zdrojového a cílového prvku pole při práci s blokem
paměti. Většina aritmeticko-logických instrukcí však nerozlišuje mezi aritmetickými a
indexovými registry, obojí lze používat v operandech instrukcí.
Lokální proměnné i návratové hodnoty podprocedur se obvykle ukládají do vyhrazeného
úseku paměti na zásobník, k indexaci zásobníku pak slouží dva 16-bitové registry SP (Stack
Pointer) a BP (base pointer). SP ukazuje na jeho vrcholek, BP lze použít pro odkaz na lokální
rámec procedury.
Registry AX, BX, CX, DX, SI, DI, SP a BP se označují též jako obecné registry.
Každá instrukce pracující s pamětí potřebuje paměť adresovat, určit segment a ofset
paměti. Pro označení segmentu se používají 4 segmentové registry. Registry CS (Code Segment)
XIII
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci
a SS (Stack Segment) mají fixní význam, označují segment s právě vykonávaným kódem a
zásobníkový segment. Registry DS (Data Segment) a ES (Extra Segment) jsou používány
k adresaci při práci s daty v paměti. Všechny segmentové registry jsou 16-bitové.
Registr IP (Instruction Pointer) určuje ofset právě vykonávané instrukce v kódovém
segmentu, žádná instrukce procesoru neobsahuje variantu, ve které by registr IP explicitně
vystupoval; načíst jeho hodnotu, či ji změnit, lze instrukcemi větvení či přerušením.
Instrukce
Instrukce procesoru mohou mít žádný, jeden či dva operandy, přičemž některé instrukce
umožňují poměrně širokou škálu možných typů operandů (registr, paměť, konstanta) jiné jsou
pevně spjaty jen s jediným registrem.
Procesor nabízí standardní aritmeticko-logické instrukce pro sčítání (ADD, ADC, INC),
odčítání (SUB, SBB, DEC), porovnávání (CMP), bitové operace posunutí (SHR, SHL, SAR, ROL, RCL,
ROR, RCR), maskování (AND, XOR, OR), testování (TEST) apod. Ve všech těchto instrukcích (až
na instrukce posunu) většinou vystupují dva operandy, jeden je vždy cílový (pro zápis výsledku),
přičemž operandem může být buď libovolný aritmeticko-logický registr či indexový registr.
Zdrojovým operandem může být též libovolná adresa v paměti či konstanta.
Procesor obsahuje instrukce pro vkládání (PUSH) 16-bitových dat na zásobník a odebírání
(POP) z něj. Dále má celou sadu instrukcí pro práci s čísly v desítkové soustavě, řídící instrukce
pro povolení/zákaz přerušení apod.
Předřazením speciálního prefixu (REP, REPE, REPNE) před některé instrukce (MOVS, LODS,
STOS, CMPS, SCAS) se docílí opakování dané instrukce pro celý souvislý blok dat, prefixy jsou
použitelné pro přesuny, výmazy či prohledání souvislých bloků paměti.
Dále obsahuje instrukce (IN, OUT) pro komunikaci s periferními zařízeními. Komunikace
je vedena přes fixní čísla bran (Ports).
Procesor disponuje i instrukcemi pro násobení a dělení celými a přirozenými čísly (MUL,
IMUL).
Instrukce nepodmíněného větvení s návratem (CALL) a bez návratu (JMP), a podmíněné
instrukce větvení bez návratu (Jcc, kde cc je kombinací několika písmen označující typ
podmínky) pracují buď s přímou či nepřímou absolutní adresou či přímou relativní adresou,
operand může být 8, 16 či 32-bitový (ne všechny tyto kombinace jsou povoleny pro všechny
uvedené instrukce větvení). Instrukce návratu z podprogramu (RET) se vrací na adresu uloženou
na vrcholku zásobníku (kam byla uložena instrukcí CALL).
Instrukce XLAT slouží pro překlad bytů pomocí překladové tabulky a její přítomnost
v instrukčním repertoáru procesoru jednoznačně prokazuje jeho CISC-ový původ.
Instrukcí INT lze synchronně vyvolat přerušení (provede se skok na obslužnou rutinu
přerušení).
Procesor má podporu multiprocesových systémů. Obsahuje prefix LOCK, který zabrání
při provádění instrukce přístupu ke sběrnici ostatním procesorům. Je tím dovolena implementace
semaforů.
Kódování instrukcí
Kódování instrukcí je poměrně složité, instrukce mohou mít délku 1 až 15 bytů (včetně
všech prefixů a operandů). Každá instrukce se může skládat až ze čtyř částí. Nejprve jsou
uvedeny případné prefixy (viz odstavec „Prefixy instrukcí procesorů IA-16 a IA-32“ kapitoly
B.5.1 dodatku B), pak následuje operační kód instrukce (jediná povinná část kódování instrukce),
za ním je byte modality (R/M Byte) a následuje kódování žádné, jedné či dvou konstant.
XIV
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci
Operandy instrukce jsou určeny buď implicitně v operačním kódu instrukce či v bytu modality
instrukce.
Příznaky (Flags)
Jeden 16-bitový registr procesoru (všechny bity však nejsou využity) vystupuje jako
stavový (PSW –Processor Status Word) – obsahuje příznaky, které buď ovlivňují funkci
některých instrukcí či obsahují výsledek logických operací, tabulka ukazuje rozložení příznaků
v registru.
11
10
9
8
7
6
5
4
3
2
1
0
OF
DF
IF
TF
SF
ZF
0
AF
0
PF
1
CF
tab. 14 – rozložení příznaků u procesoru Intel 8086
Bity číslo 1, 3, 5 a 12 až 15 jsou rezervovány, bity DF, IF a TF jsou bity řídící (lze jimi
ovlivnit chod některých instrukcí či práci celého procesoru), ostatní bity jsou bity stavové (jsou
nastavovány po vykonání většiny aritmeticko-logických instrukcí a ovlivňují instrukci
podmíněného větvení). Význam bitů je následující.
Bit DF (Direction Flag) určuje směr průchodu blokem paměti při použití blokových
instrukcí s prefixem. Při vynulovaném bitu (lze docílit instrukcí CLD) se postupuje směrem
k vyšším adresám, při nastaveném bitu (instrukcí STD) opačně.
Bit IF (Interrupt Flag) povoluje a zakazuje spuštění obslužných rutin vyskytnuvších se
přerušení, kromě nemaskovatelného (NMI – Non-Maskable Interrupt), které nelze programově
zakázat14. Při nastaveném bitu (instrukcí STI) jsou přerušení povolena, jinak jsou zakázána
(instrukcí CLI).
Bit TF (Trap Flag) slouží k ladícím účelům. Při jeho nastavení je po každé instrukci
generováno přerušení krokování. Hodnotu bitu nelze měnit přímo, obvykle se nastaví sekvencí
následujících instrukcí:
...
PUSHF
POP
OR
PUSH
POPF
RET
...
AX
AX,10h
AX
;
;
;
;
;
;
;
uložení registru s příznaky na zásobník
načtení příznaků do registru AX
nastavení krokovacího příznaku TF
uložení upraveného slova zpět na zásobník
zápis do registru příznaků
tato instrukce se ještě „nekrokuje“, až první
následující
příklad 54 – nastavení příznaku krokování
Příznaky OF (Overflow Flag), SF (Sign Flag), ZF (Zero Flag), AF (Auxiliary Carry
Flag), PF (Parity Flag) a CF (Carry Flag) jsou stavové bity, jejich hodnoty jsou nastavovány
po vykonání některých aritmeticko-logických instrukcí, zároveň ovlivňují provedení instrukce
podmíněného větvení. OF je nastaven, pokud dojde k přetečení čísla se znaménkem. SF je
nastaven, pokud je výsledek záporný. ZF indikuje, že výsledek operace byl nulový. AF
signalizuje přenos mezi bity číslo 3 a 4, používá se pro BCD aritmetiku. PF společně s nejnižším
bytem výsledku určuje lichou paritu – je nastaven, pokud je počet jedničkových bitů ve spodním
bytu sudý. CF signalizuje přetečení při bezznaménkové aritmetice, s jeho pomocí lze
naprogramovat aritmetiku čísel větších než 16 bitů.
14
na počítačích PC AT lze NMI zakázat pomocí externího hardware, nastavením 7. bitu brány číslo 0x80
XV
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci
Systém přerušení
Mikroprocesor Intel 8086 používá vektorový systém přerušení. Každé přerušení je
identifikováno 8-bitovým číslem, na začátku paměti je uložena tabulka s adresami obslužných
rutin jednotlivých přerušení (tzv. vektory přerušení). Záznamy v tabulce jsou 32-bitové, obsahují
segment a ofset adresy.
Při výskytu přerušení číslo N se uloží aktuální stav procesoru (adresa vykonávané
instrukce a registr s příznaky) na zásobník, vynulují se příznaky IF (zabrání zanoření obslužné
rutiny) a TF (zabrání krokování obslužné rutiny) a přejde se na adresu uložené v N-tém záznamu
tabulky vektorů přerušení (nastaví se registry CS a IP). Pokud vykonávaná rutina změní hodnoty
některých registrů, musí se postarat o obnovu jejich hodnot před návratem z přerušení (původně
vykonávaný kód nemá potuchy o vzniklém přerušení).
Tento systém umožňuje jednoduše nahrazovat obslužné rutiny vlastními, případně
připojovat více rutin k jednomu přerušení (rutinu lze přidat tak, aby při svém skončení zavolala
rutinu původní).
Firma Intel si vyhradila pro výskyt prvních 32 přerušení vlastní rutiny sloužící
k zachycení některých chybových či jiných stavů procesoru15.
0x00
Celočíselné dělení nulou (Divide by Zero)
0x01
Krokování programu (Single-step), pokud je TF=1
0x02
Nemaskovatelné přerušení (NMI), při HW chybě
0x03
Zarážka (Breakpoint Trap), vyvoláno synchronně instrukcí INT3
0x04
Přetečení (Overflow Trap), vyvoláno synchronně instrukcí INTO
tab. 15 – seznam přerušení procesoru Intel 8086
K uvedeným přerušením procesoru existují i další přerušení generována externími
perifériemi (časovač, klávesnice apod.), tato přerušení jsou volána asynchronně. Mimoto je
možno jednotlivá přerušení volat synchronně (programově) – spuštěním instrukce INT16.
Přerušení procesoru se může vyskytnout mezi libovolnými dvěmi instrukcemi (pokud
není přerušení zakázáno17) a to by činilo problém v případě nastavení změny adresy zásobníku.
Je třeba nastavit segment i ofset adresy, to však nelze udělat atomicky jedinou instrukcí. Pokud
by se přerušení vyskytlo právě ve chvíli, kdy je změněna pouze jedna část a druhá zůstala
původní, procesor by při ukládání návratové adresy a registru příznaků pravděpodobně zapsal
na nepatřičnou adresu. Z tohoto důvodu existuje v procesoru mechanismus, který zajistí, že
po instrukci zápisu do segmentové registru zásobníku (registr SS) nenastane přerušení. Pokud
následuje instrukce nastavení ofsetu adresy zásobníku (registr SP), je změna provedena vždy
korektně, viz následující příklad.
...
MOV
MOV
...
SS,segment
SP,offset
; nastavení segmentu, přerušení se negeruje
; následuje nastavení ofsetu
příklad 55 – korektní nastavení adresy zásobníku
15
bohužel, výrobci počítačů nerespektují toto pravidlo a používají takto vyhrazené vektory přerušení
pro správu vlastních periférií, což způsobuje nemalé problémy
16
což má podobný efekt jako volání podprogramu (navíc se na zásobník ukládá registr s příznaky), tímto
způsobem se například používají služby BIOSu a operačního systému MS-DOS
17
a nehledě na to, že nemaskovatelné přerušení nelze zakázat
XVI
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci
Vnitřní architektura
Z hlediska vnitřní stavby lze procesor rozdělit na dvě části: sběrnicovou (BIU – Bus
Interface Unit) a prováděcí (EU – Execution Unit). BIU vybírá instrukce z paměti a ukládá je
do vnitřní fronty, ze které je čte EU. BIU také řídí přenos dat po sběrnicích. Tím, že obě jednotky
pracují nezávisle, se zvyšuje výkon procesoru. Například v době, kdy EU provádí instrukci, čte
BIU z paměti další instrukce. Tuto spolupráci narušují pouze instrukce skoku, při kterých musí
BIU vyprázdnit frontu instrukcí a začít číst instrukce z nové adresy. Levnější verze procesoru
Intel 8088 má pouze 8-bitovou sběrnici (BIU), všechny přenosy dat jsou tak pomalejší.
B.3. Procesor Intel 80186
Procesor Intel 80186 se liší jen velmi málo od svého předchůdce. Přidává několik nových
instrukcí a jedno přerušení. Používal se hlavně ve výkonnějších perifériích, v počítačích jen
málo.
Instrukce
Přidává nové instrukce PUSHA a POPA pro ukládání a obnovu registrů AX, BX, CX, DX, SI,
DI, SP a BP, instrukci BOUND pro kontrolu přetečení mezí, pokud test neprojde, instrukce generuje
přerušení číslo 0x05 a instrukce INS a OUTS pro zápis bloku dat na periferní zařízení.
Dále rozšiřuje možnosti instrukcí PUSH, IMUL a instrukcí bitového posunu o další operand
– konstantu.
Podpora vyšších jazyků
Instrukce ENTER a LEAVE slouží k podpoře vyšších jazyků. Instrukce ENTER ukládá
hodnotu registru BP, kopíruje odkazy na rámce vyšších procedur18 a vytváří na zásobníku rámec
pro ukládání lokálních proměnných procedury, zároveň nastavuje registr BP tak, aby ukazoval
do vytvořeného rámce. Naopak instrukce LEAVE má práci usnadněnou, zruší vytvořený rámec
pro lokální proměnné (zkopírováním hodnoty registru BP do SP) a ze zásobníku obnoví hodnotu
registru BP19.
Přerušení
K původním pěti přerušením přidává další přerušení, které generuje instrukce BOUND,
pokud test nevyhověl zadaným mezím.
B.4. Procesor Intel 80286
B.4.1.
Reálný režim
Intel 80286 zavádí dva pracovní režimy, reálný režim (Real Mode) je nastaven
po inicializaci procesoru a procesor v něm běžící je zpětně kompatibilní se staršími verzemi.
Přesto jsou zavedeny i zde některé změny a rozšíření, které jsou následně popsány20.
Instrukce rotace
Maximální počet bitů, o kolik instrukce rotace posouvá svůj argument, je oříznut na 31
bitů.
18
které je možno zanořovat podobně jako v jazyce PASCAL
instrukce má v praxi smysl používat jen na 16-bitových verzích procesoru, na vyšších verzí je co
do rychlosti optimálnější používat sekvenci jednodušších instrukcí, které zajistí stejnou funkcionalitu
20
změny popisované pro reálný režim práce platí i v chráněném režimu
19
XVII
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci
Vektor přerušení
Vektor s adresami přerušení nemusí ležet na adrese 0000:0000, ale na libovolné adrese
určené hodnotou nového registru IDTR (Interrupt Descriptor Table Register) o velikosti 6 bytů
(limit tabulky – 2 byty, lineární adresa tabulky – 3 byty, nevyužito – 1 byte).
Nekompatibilní změny
Instrukce PUSH SP ukládá u procesoru Intel 80286 hodnotu před dekrementací registru SP,
jeho předchůdce ukládá na zásobník již dekrementovanou hodnotu (samozřejmě se tímto změnilo
i chování instrukce POP SP). Ačkoli je tato vlastnost nekompatibilitou vůči starší verzi, nečiní
tato změna větší potíže aplikačním programům. Jen málokterý běžný program pracuje s takto
uloženou hodnotou na zásobníku21.
Změna je též v chování procesoru při výskytu přerušení číslo 0. Původně se na zásobník
uložila adresa následující za instrukcí, která přerušení způsobila, nyní se ukládá adresa této
instrukce22.
B.4.2.
Chráněný režim
Největší změnou tohoto procesoru od minulé verze je zavedení nového pracovního
režimu, tzv. chráněného módu (Protected Mode). Operační systém pracující v novém režimu
práce má konečně prostředky k ochraně paměti a periférií před nežádoucím přístupem ze strany
aplikačních programů. Tento režim zároveň zavádí podporu vícero běžících úloh současně
(multitasking) s možností jejich preemptivního přepínání. Díky 24-bitové adresové sběrnici má
procesor možnost používat až 16MB fyzické paměti, i když pouze v chráněném módu procesoru.
V následujících kapitolách jsou popsány hlavní vlastnosti chráněného režimu práce –
adresace paměti, systém ochrany a podpora spouštění více úloh (multitasking).
Adresace paměti
Způsob adresace se značně zesložitil. Každý ze segmentových registrů (nyní nazýván
selektor) ukazuje do tabulky popisovačů segmentů. Tabulka obsahuje až 8192 položek (nultá
položka je neplatná) o velikosti 8 bytů, každá položka obsahuje informaci o jednom segmentu.
Báze (počáteční adresa) každého segmentu je určena 24 bity. Maximální velikost segmentu
(limit) zůstává na 65536 bytech. Tabulka deskriptorů může být lokální (LDT – Local Descriptor
Table), či globální (GDT – Global Descriptor Table). Z 16-bitové viditelné hodnoty selektoru je
13 bitů použito na adresu do tabulky deskriptorů, 1 bit určuje, zda-li se jedná o tabulku lokální (je
vlastní každé úloze) či globální (sdílejí ji všechny úlohy) a 2 bity stanoví úroveň oprávnění.
Následující obrázek znázorňuje způsob výpočtu fyzické adresy ze selektoru a ofsetu.
21
motivace této změny je průhledná – jedná se o opravu chyby staršího procesoru, posloupnost instrukcí
PUSH SP / POP SP se na předchozích procesorech nechová korektně
22
opět jde o opravu chyby – na procesorech IA-16 a IA-32, kde instrukce nemají stejnou délku kódování,
nelze obecně z adresy instrukce zjistit adresu předcházející instrukce
XVIII
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci
Virtuální adresa
Selektor
Ofset
Index do tabulky deskriptorů
TI RPL Posunutí v rámci segmentu
15
3 2 1 0 15
TI (Table Indicator) – indikuje, zda-li selektor ukazuje do tabulky lokální (1) či
globální (0)
Fyzická paměť
RPL (Requested Priviledge Level)
0
16MB
Tabulka deskriptorů
0. položka neplatná
Velikost
segmentu je
dána limitem
N. položka
63
0
Max. 8192 položek
o velikosti 8 bytů
0
Deskriptor na N.tém místě
Reservováno P DPL
Různé Báze Limit
63
48 47 46 45 44 40 39 16 15 0
Reservováno – mělo by mít hodnotu 0, na následujících verzích procesoru je používáno
P – indikuje, zda-li je popisovaný segment platný (v paměti), je možno využít
pro virtualizaci paměti
DPL (Descriptor Priviledge Level) – úroveň oprávnění segmentu, který deskriptor popisuje
Různé – různé využití dle typu deskriptoru
Báze – počátek segmentu ve fyzické paměti
Limit – maximální velikost segmentu
obrázek 7 – převod efektivní adresy na lineární ve chráněném módu procesoru23
Systém ochrany
Ochrana paměti a periférií je realizována přiřazením priority programu (resp. segmentu,
z kterého jsou program či jeho část spouštěny). Procesor rozeznává čtyři úrovně priorit, které
jsou označeny čísly 0 až 3.
Úroveň 0 (nejvyšší) je určena pro jádro OS. V této úrovni je povoleno nejvíce (resp.
všechny) privilegovaných instrukcí. Úroveň 1 je určena pro doplňky OS, jako jsou ovladače
apod. Priorita číslo 2 je pro dobře otestované systémy, např. překladače, vývojové prostředky
apod. Úroveň 3 (nejnižší) je pro vše ostatní, především aplikační programy. Má zakázáno téměř
vše. V praxi se používají úrovně číslo 0 a 3.
23
obrázek neznázorňuje všechny varianty deskriptoru; Intel 80286 má několik variant deskriptorů pro
různá použití, každá z variant může mít jiné, či jinak dlouhé, datové položky; toto je varianta deskriptoru
popisující kódový či datový segment
XIX
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci
Mluvit o výši úrovni priorit může být poněkud matoucí, logicky vyšší úroveň (má
povoleno více prostředků) má numericky nižší hodnotu. V následujícím textu se vždy hovoří
o numerické hodnotě priorit.
Ochrana paměti rozeznává dva pojmy24 – aktuální úroveň priority (CPL – Current
Privilege Level) a úroveň priority deskriptoru (DPL – Descriptor Privilege Level). CPL je
priorita, kterou obdržel právě běžící proces (resp. je to DPL segmentu, kde je uložen kód
běžícího procesu). Proces může pracovat pouze s deskriptory, jejichž DPL je (numericky) nižší či
rovno CPL procesu (proces má alespoň takovou úroveň priority jako je úroveň deskriptoru).
Kontrola úrovně priorit je provedena při každém zápisu do selektoru25.
Podobným způsobem je řešen i přístup k perifériím, tedy použití instrukcí CLI, STI, IN,
OUT, INS, OUTS a INT. Bity číslo 12 a 13 v příznakovém registru určují úroveň priority
vstupně/výstupních zařízení (IOPL – Input/Output Privilege Level). Pokud je hodnota IOPL
vyšší či rovna CPL procesu, má proces právo používat jakkoli instrukce STI, CLI (povolení,
zakázání generování maskovatelných přerušení) i IN, INS (čtení dat z periférie) a OUT, OUTS
(zápis dat na periférii). Použití instrukce INT (synchronní vyvolání obsluhy přerušení) je
regulováno přítomností deskriptoru daného přerušení v tabulce deskriptorů přerušení ( IDT –
Interrupt Descriptor Table), pokud není položka přítomna – limit tabulky je menší než číslo
přerušení (krát velikost deskriptoru), či je položka přítomna, ale DPL deskriptoru je numericky
nižší než CPL procesu, je porušen systém ochrany a instrukce se nevykoná.
K ochraně paměti navíc procesor nabízí instrukci ARPL, která kontroluje, zda-li proces
poskytující selektor operačnímu systému má k danému segmentu dostatečná oprávnění
k přístupu. Uživatelský proces, který chce, aby operační systém vykonal nějako operaci
v datovém segmentu, poskytne selektor tohoto segmentu operačnímu systému (např. jako
parametr na zásobníku) a do spodních dvou bitů zapíše žádanou prioritu (RPL – Requested
Privilege Level). Tato priorita musí být numericky nižší nebo stejná jako je DPL datového
segmentu. Operační systém použije instrukci ARPL ke kontrole toho, zda-li má proces vůbec
právo přistupovat k datovému segmentu v žádané prioritě. Instrukce ARPL má dva argumenty –
selektor procesu (uložený na zásobníku) a selektor datového segmentu (parametr).
Veškeré porušení systému ochrany má za následek vyhození výjimky číslo 13 (0x0D),
kterou by měl ošetřit operační systém běžící na nejvyšší úrovni priorit. Při výskytu výjimky číslo
13 je na zásobníku mimo jiné uložena adresa instrukce, jež porušení ochrany způsobila.
Zde objasněný systém ochrany není popsán kompletně. Váže se k němu spousta dalších
pravidel – každá úroveň oprávnění má svůj vlastní zásobník, nelze zapisovat do kódového
segmentu, lze nastavit (pomocí určitého bitu v deskriptoru), zda-li je povolen zápis do datového
segmentu či čtení z kódového segmentu (čtení dat z datového segmentu a spouštění kódu
z kódového segmentu je povoleno vždy), zásobníkový segment musí být datový s povolením
zápisu, do registru CS nelze umístit jiný selektor než kódového segmentu, do DS i ES lze umístit
selektor kódového segmentu (aby to však mělo smysl, mělo by být navíc povoleno čtení
z kódového segmentu) atd. Podrobné popsání všech pravidel systému ochrany by vyžadovalo
mnoho prostoru, pro účely předkládaného textu však nejsou tyto skutečnosti důležité.
24
ve skutečnosti existuje i třetí pojem – žádaná úroveň priority (RPL – Requested Privilege Level), která
je uložena ve spodních 2 bitech selektoru, tento pojem je vysvětlen dále
25
z programového hlediska to jsou původní segmentové registry CS, ES, DS a SS
XX
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci
Podpora spouštění více úloh (Multitasking)
Podpora multitaskingu zavádí strukturu, do které se ukládá stav procesoru (TSS – Task
Segment State), každá úloha v procesoru má právě jednu strukturu TSS, její formát je
v následující tabulce.
Význam
ofset
Význam
ofset
Odkaz na předchozí úlohu
0x00
0x16
Registr DX
0x02
0x18
Registr SP procesu priority 0
Registr BX
Selektor SS procesu priority 0 0x04
0x1A
Registr SP
0x06
Registr SP procesu priority 1
0x1C
Registr BP
0x1E
Selektor SS procesu priority 1 0x08
Registr SI
0x0A
Registr SP procesu priority 2
0x20
Registr DI
Selektor SS procesu priority 2 0x0C
0x22
Selektor ES
0x0E
Registr IP (vstupní bod)
0x24
Selektor CS
0x00
0x26
Registr příznaků FLAGS
Selektor SS
0x12
Registr AX
0x28
Selektor DS
0x14
Registr CX
0x2A
Selektor LDT
tab. 16 – stavový segment úlohy procesoru Intel 80286
Po přepnutí do chráněného režimu práce, není aktivní žádná úloha. Operační systém by
měl vyplnit registr TR (instrukcí LDTR), který obsahuje selektor do globální tabulky deskriptorů
(GDT). Deskriptor popisuje segment, jež obsahuje celou strukturu TSS, že jde o deskriptor
popisující TSS je indikováno bity číslo 40 až 43 v deskriptoru. Pokud se provede vzdálený skok
(JMP FAR) a selektoru CS připadne deskriptor TSS, je provedeno přepnutí úloh – původní TSS je
nastaveno na neaktivní (bity v deskriptoru) a nové TSS je nastaveno jako aktivní. Přepnutí úloh
může být provedeno i vzdáleným voláním (CALL FAR), v tomto případě zůstává původní úloha
stále aktivní, do položky na ofsetu 0 struktury TSS se uloží selektor na původní úlohu a jako
aktivní se označí i nová úloha (může tak být aktivních i vícero úloh). Zpracovávaná úloha
nemůže předat řízení jiné aktivní úloze. Registr TR obsahuje selektor (a v neviditelné části i
deskriptor) aktuálně zpracovávané úlohy.
Procesor neukládá přímo kontext koprocesoru, přepnutí úloh je však indikováno 3. bitem
v registru MSW (viz dále), kontext koprocesoru je pak možno uložit pouze v případě, že i nová
úloha koprocesor používá. Přepínání úloh je možno provádět i nastavením 14. bitu
v příznakovém registru a zavoláním instrukce IRET.
Nové registry a instrukce chráněného módu
Chráněný mód zavádí nové registry pro práci se systémem ochrany a několik instrukcí
pro práci s nimi.
Funkce bitů registru MSW (Machine Status Word) je následující.
Bit číslo 0 (PE – Protection Enabled) indikuje, zda-li procesor pracuje v reálném či
v chráněném režimu práce. Přepnutí do chráněného režimu se děje právě nastavením bitu PE.
Zároveň je však nutno nastavit správně globální tabulku deskriptorů a selektory DS, ES a SS.
XXI
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci
Selektor CS se nastaví instrukcí vzdáleného skoku (na vhodnou adresu). Firma Intel nepočítala
s návratem do reálného módu procesoru, bit PE nelze vynulovat26.
Bit číslo 1 (MP – Math Present) určuje, zda-li je přítomen matematický koprocesor.
Společně s dalším bitem (EM – EMulate) ovlivňuje chování procesoru při výskytu instrukce
FPU. Bit EM by měl být nastaven, pokud je přítomen emulátor koprocesoru. Při výskytu
instrukce FPU si procesor podle bitu MP otestuje, zda-li je přítomen koprocesor, pokud ano,
předá mu instrukci, pokud ne, otestuje, zda-li je nastaven bit EM, pokud je bit EM nastaven,
vyvolá přerušení číslo 7, jinak vyvolá přerušení číslo 6. Emulátor koprocesoru by měl obsadit
přerušení číslo 7, ze zásobníku si převzít adresu instrukce, jež přerušení způsobila, a tu
odemulovat.
Bit číslo 3 (TS – Task Switched) indikuje, že došlo k přepnutí úlohy. Pokud se vyskytne
instrukce koprocesoru při nastaveném bitu TS, zavolá se vždy přerušení číslo 7, zde by měl OS
změnit kontext koprocesoru. Lze tak snadno implementovat ukládání kontextu koprocesoru
pouze v případech, že nová úloha koprocesor skutečně využívá.
Ostatních 12 bitů jsou procesorem Intel 80286 nevyužity a zůstávají vždy rovny 0.
Instrukce SMSW (Store MSW) naplní obecný registr obsahem registru MSW. Tato instrukce
je povolena i v reálném módu a v chráněném módu při libovolné úrovni oprávnění. Lze tak
kdykoli (i aplikačním programům) testovat přítomnost koprocesoru, emulátoru koprocesoru,
přepnutí úloh či režim, v kterém aplikační program vlastně běží.
Instrukce LMSW (Load MSW) zapíše obecný registr do registru MSW. Tato instrukce je
povolena pouze při nejvyšší úrovni oprávnění (numericky rovné 0), je však možné ji použít i
v reálném módu, např. pro přepnutí do chráněného režimu.
Registry GDTR (Global Descriptor Table Register) a IDTR (Interrupt Descriptor Table
Register) jsou 48-bitové (horní byte není využit) a obsahují bázi a limit globální tabulky
deskriptorů (GDT) a tabulky deskriptorů přerušení (IDT). Registry LDTR (Local Descriptor Table
Register) a TR (Task Register) obsahují selektor to globální tabulky deskriptorů (GDT)27.
Instrukce LGDT, SGDT, LIDT a SIDT zapisují do registrů GDTR či IDTR hodnotu obecného
registru, či naplní obecný registr hodnotou z registru GDTR či IDTR. Jsou povoleny v reálném
režimu či při CPL rovné 0.
Instrukce LLDT, SLDT, LTR a STR provádí stejnou funkcionalitu pro registry LDT a TR, jsou
povoleny pouze v chráněném režimu při CPL rovné 0.
Instrukce LAR, LSL, VERR a VERW jsou povoleny v chráněném režimu při jakékoli úrovni
oprávnění a poskytují následující funkcionalitu. LAR naplní obecný registr přístupovými právy
(možnost zápisu, čtení, vykonání kódu, přítomnost v paměti apod.) segmentu určeného
selektorem, LSL naplní obecný registr limitem segmentu určeného selektorem, VERR testuje daný
segment, zda je způsobilý ke čtení a VERW testuje, zda je segment zapisovatelný.
Instrukce CTS je povolena jen při CPL rovné 0, shazuje příznak TS v registru MSW.
U instrukcí větvení INT, CALL, JMP, RETF a IRET byla přidána kontrola porušení systému
ochrany a výměna zásobníku při změně úrovně priorit.
26
ačkoli není návrat do reálného módu přímo podporován, je možné jej uskutečnit pomocí BIOSu
počítače, který umožňuje procesor resetovat (nové spuštění procesoru), zároveň je možno definovat, na
které adrese má procesor začínat; tento způsob však nemusí fungovat na všech počítačích PC
27
zároveň obsahují i neviditelnou část s deskriptorem určeným selektorem, toto je nutno si uvědomit,
změna v deskriptoru se nepromítne ihned do registrů LDTR či TR
XXII
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci
Příznaky
V chráněném režimu se uplatňují 3 nové bity v příznakovém registru. Kompletní sada
tedy vypadá následovně:
15 14 13 12 11
10
9
8
7
6
5
4
3
2
1
0
0 NT
IOPL
OF DF
IF
TF
SF
ZF
0
AF
0
PF
1
CF
tab. 17 – příznaky procesoru Intel 80286
Bity IOPL (I/O Privilege Level) určují úroveň oprávnění, při které může proces ještě
provádět V/V instrukce. Vyšší hodnota představuje nižší úroveň oprávnění.
Bit NT (Nested Task) určuje režim práce instrukce IRET. Je-li NT rovno 0, provádí IRET
klasický návrat z přerušení. Je-li NT rovno 1, přepne se při provádění IRET proces podle
zpětného ukazatele právě aktivního TSS.
Ostatní příznaky mají stejný význam jako u minulé verze procesoru.
Vektory přerušení
Kromě původních vektorů přerušení, procesor zavádí další vektory, především
v souvislosti s chráněným režimem práce. Některá přerušení fungují jako hlášení (na zásobníku
je uložena adresa následující za instrukcí, která přerušení způsobila), jiné instrukce signalizují
chybu (na zásobníku se ukládá adresa instrukce, jež přerušení zapříčinila). Následující tabulka
uvádí seznam všech přerušení procesoru.
Zavedl Vektor Význam
8086
0x00 Celočíselné dělení nulou (Divide by Zero)
8086
0x01 Krokování programu (Single-step), pokud je TF=1
8086
0x02 Nemaskovatelné přerušení (NMI), při HW chybě
8086
0x03 Zarážka (Breakpoint Trap), vyvoláno synchronně instrukcí INT3
8086
0x04 Přetečení (Overflow Trap), vyvoláno synchronně instrukcí INTO
80186
0x05 Číslo neleží v zadaném intervalu, generováno instrukcí BOUND
80286
0x06 Neznámá instrukce, i pokud instrukce chráněného módu použita v reál. módu
80286
0x07 Emulace koprocesoru, pokud nastavena emulace v registru MSW
80286
0x08 Vnořená chyba (Double Fault), pokud výskyt chyby při ošetřování chyby
80286
0x09 Koprocesor adresuje mimo segment
80286
0x0A Jakákoli chyba při přepínání úloh
80286
0x0B Výpadek segmentu, pokus o přístupu k segmentu, který není v paměti
80286
0x0C Výpadek segmentu zásobníku či jeho podtečení
80286
0x0D Obecná chyba ochrany, generováno při jakémkoli porušení systému ochrany
tab. 18 – seznam přerušení procesoru Intel 80286
Shrnutí
Vlastnosti chráněného módu procesoru Intel 80286 jsou zde popsány velmi stručně,
podrobnější popis lze nalézt v [CADA91], [VAGN94] a kompletně v [INTL99c]. Důležité je, že
procesor poskytuje, oproti starším verzím, dostatečnou ochranu paměti i periférií
před neoprávněným přístupem ze strany aplikačních programů a že implementuje rozsáhlou
podporu multitaskingu s možností preemptivního přepínání úloh. Bohužel, nepodporuje spouštění
reálného kódu v rámci chráněného režimu práce.
XXIII
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci
B.5. Procesor Intel 80386
Intel 80386 je prvním plně 32-bitovým procesorem v této řadě, rozšiřuje velikost všech
„nesegmentových“ registrů. Umožňuje pracovat až se 4GB fyzické paměti a teoreticky až s 64TB
virtuální paměti. Zachovává všechny vlastnosti chráněného módu předešlého procesoru a navíc
přidává možnost stránkování - virtualizace paměti28. Poskytuje nové registry i instrukce
pro ladící a testovací účely. Z chráněného režimu práce se lze „legálně“ přepnout zpět do
reálného režimu, navíc je tu možnost spouštět úlohy reálného v rámci chráněného režimu.
B.5.1.
Reálný režim
Registry
Původní 16-bitové obecné registry (ne segmentové) se rozšířily na 32-bitů, ačkoli jejich
spodní část se dál dá používat k 16-bitovým operacím29. K indikaci toho, že jde o 32-bitové
registry, je před název původního 16-bitového obecného registru přidáno písmeno E. Rozšířeny
jsou i řídící registry – ukazatel instrukcí (EIP) a registr příznaků (EFLAGS). Jsou přidány dva nové
segmentové registry (v chráněném režimu fungující jako selektory) FS a GS. Pro testovací účely
je nově k dispozici 8 ladících registrů DR0 (Debug Register) až DR7 (DR5 a DR6 jsou rezervovány).
Nové jsou registry CR0 až CR3 (Control Register). Dolní polovina registru CR0 je identická
s registrem MSW, CR0 obsahuje řídící bity podobně jako registr EFLAGS, registr CR1 je rezervován,
registry CR2 a CR3 jsou použity pro stránkování. U registrů GDTR a IDTR se používá i nejvyšší,
původně rezervovaný, byte. Přesný popis registrů lze nalézt v [INTL99c], [CADA91] či
[VAGN94].
Instrukce
Procesor Intel 80386 je zpětně kompatibilní s procesorem Intel 80286, zachovává všechny
jeho instrukce (ačkoli je lepší z důvodu rychlosti některé instrukce nahradit posloupností jiných,
viz kapitola 5).
Rozšíření nastává u instrukcí:
• instrukce podmíněného větvení – rozšiřuje velikost relativního ofsetu až na 32-bitů
(původně jen 8-bitů)
• nové varianty instrukce MOV pro zápis do a čtení z registrů CRx a DRx.
• IMUL, PUSH a POP možnost 32-bitových operandů
Jsou přidány nové instrukce:
• LFS a LGS pro zápis do segmentových registrů FS a GS
• SETcc pro nastavení 8-bitového registru na hodnotu 0 či 1 dle platnosti vyhodnocené
podmínky (podmíněné nastavení)
• SHLD, SHRD pro rotaci bitů doleva (resp. doprava) ve dvou operandech najednou
• BSF, BSR pro nalezení nejnižšího (resp. nejvyššího) nenulového bitu
• BT, BTC, BTR, BTS k přečtení určitého bitu, k jeho vynulování či nastavení
• MOVZX, MOVSX pro přesuny čísel mezi 8-bitovým či 16-bitovým a 32-bitovým
operandem, kdy je kratší operand doplněn 0 (resp. je znaménkově rozšířen)
28
virtualizaci paměti však lze realizovat i pomocí segmentace zavedené procesorem Intel 80286
např. nový 32-bitový registr EAX má spodních 16 bitů shodných s registrem AX (zůstává i dělení
na registry AL a AH), horních 16 bitů 32-bitového registru nejsou přístupné přímo
29
XXIV
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci
Kódování instrukcí s 32-bitovými operandy
V reálném i chráněném režimu práce je možno používat u instrukcí jak 32-bitové, tak 16bitové registry, k tomuto účelu je zaveden prefix velikosti operandu (Operand-Size Override
Prefix). V reálném módu je implicitně nastavena 16-bitová velikost operandů, v chráněném módu
lze určit v nastavení kódového segmentu, zda-li se má kód považovat za 16-bitový či 32-bitový.
Uvedením prefixu velikosti operandu před instrukci se přepne implicitní nastavení na opačné
(z 16-bitového na 32-bitové a naopak)30.
32-bitová adresace
Původní možnosti adresace, kdy bylo možno použít jen několik kombinací jen některých
registrů pro výpočet efektivní adresy, je značně rozšířen. Opět platí, že efektivní adresa se spočte
součtem bázového a indexového registru a konstanty, kdy je možno libovolný sčítanec vynechat
(musí tam samozřejmě zůstat aspoň jeden). Všechny použité registry jsou 32-bitové, konstanta
může být 8 či 32-bitová. Bázovým či indexovým registrem může být libovolný obecný registr.
Indexový registr je navíc možno vynásobit konstantou o velikosti 1, 2, 4 či 8.
K rozlišení toho, zda-li se použije původní 16-bitová adresace či nová 32-bitová, slouží
prefix adresace (Address-Size Override Prefix) podobně jako v případě prefixu velikosti
operandů. Opět je v reálném režimu použita implicitně 16-bitová adresace, v chráněném režimu
lze toto určit nastavením kódového segmentu.
Prefixy instrukcí procesorů IA-16 a IA-32
Nově přidané segmentové registry, nový způsob adresace a dvoubytový operační kód
u nově přidaných instrukcí31 rozšířil množinu používaných prefixů, následující tabulka shrnuje
všechny prefixy procesorů IA-16 a IA-32.
Zavedl Hodnota Význam
8086
0x26
Změna implicitního segmentového registru na registr ES
8086
0x2E
Změna implicitního segmentového registru na registr CS
8086
0x36
Změna implicitního segmentového registru na registr SS
8086
0x3E
Změna implicitního segmentového registru na registr DS
8086
0xF0
Zamčení sběrnice (prefix LOCK)
8086
0xF2
Podmíněné opakování řetězcové instrukce (prefix REPNZ)
8086
0xF3
Nepodmíněné opakování řetězcové instrukce (prefix REP)
80836
0x64
Změna implicitního segmentového registru na registr FS
80836
0x65
Změna implicitního segmentového registru na registr GS
80836
0x66
Přepnutí velikosti operandu z 16 bitů na 32 bitů a naopak (prefix operandu)
80836
0x67
Přepnutí adresace z 16-bitové na 32-bitovou a naopak (prefix adresy)
80836
0x0F
U nových instrukcí, pokud je délka kódování instrukce bez operandů 2 byty
tab. 19 – prefixy instrukcí procesorů IA-16 a IA-32
30
instrukce s 32-bitovými operandy tak mají (až na případný prefix) stejné kódování jako instrukce s 16bitovými operandy (např. kódování MOV AX, BX je shodné s kódováním MOV EAX, EBX apod.)
31
první byte je při dekódování a vykonávání instrukce procesorů architektur P5 a P6 interpretován jako
prefix
XXV
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci
B.5.2.
Chráněný režim
Z důvodu rozšíření procesoru na 32 bitů jsou rozšířeny či pozměněny registry a struktury
chráněného módu. Registry GDTR a IDTR využívají i horní byte, struktura TSS je změněna tak,
aby mohla obsahovat 32-bitové varianty obecných registrů a registr CR332. Ofset v rámci
segmentu je 32-bitový, maximální velikost segmentu tak může být až 4GB.
Další rozsáhlejší změny a rozšíření chráněného režimu práce jsou popsány v následujících
odstavcích.
Systém ochrany přístupu k perifériím
Systém ochrany periférií je rozšířen přidáním neformátované části do struktury TSS.
Každé úloze tak lze explicitně přiřadit, ke kterým konkrétním adresám portů má úloha přístup
(nerozlišuje se zápis a čtení). Struktura TSS obsahuje (mimo jiné) relativní odkaz na bitovou
mapu, kde jeden bit indikuje, zda-li má úloha k danému portu přístup (bit = 0) či nikoli (bit = 1).
Neformátovaná část může navíc skončit s koncem limitu stavového segmentu úlohy, úloha nemá
přístup k takto nepopsaným adresám portů. Pokud je IOPL numericky vyšší či rovno CPL, jsou
všechny porty povoleny bez ohledu na obsah neformátované části TSS.
Stránkování paměti
Možnost stránkování, kterou přináší procesor Intel 80386, je velkým pomocníkem
při psaní moderních operačních systémů pracujících s virtuálním adresovým prostorem33.
Virtualizaci paměti už sice umožňuje zavedená segmentace, to, že segmenty nemají stejnou fixní
velikost, však činí tento způsob virtualizace poněkud těžkopádným.
Mechanismus stránkování převádí lineární34 adresový prostor (Linear Address Space)
na fyzický adresový prostor (Physical Address Space), přičemž stále je funkční systém
segmentace, sečtením adresy báze segmentu s ofsetem v rámci segmentu se získá lineární adresa,
ta je pak převedena mechanismem stránkování na fyzickou adresu.
Stránkování paměti je postaveno na rozdělení lineárního prostoru do stejně velkých
souvislých úseků (stránek), rozdělení fyzické paměti do bloků téže velikosti (rámce) a stanovení
způsobu mapování stránek do rámců. Lineární i fyzická adresa je dlouhá 32 bitů. Velikost stránek
(a tedy i rámců) je 4096 bytů, každá stránka (rámec) začíná na adrese dělitelné velikostí stránky.
Stránkování je dvouúrovňové, registr CR3 obsahuje odkaz na adresář stránek (Page Directory),
ten obsahuje položky, kde každá z nich odkazuje na fyzickou adresu stránkovacích tabulek35
(Page Table).
Horních 10 bitů lineární adresy je indexem do adresáře stránek, dalších 10 bitů je indexem
do příslušné stránkovací tabulky a zbytek (12 bitů) je ofsetem v rámci dané stránky. Následující
obrázek popisuje způsob převodu lineární adresy na fyzickou při zapnutém mechanismu
stránkování36.
32
každá úloha může mít vlastní stránkování
umožňuje implementovat stránkování na žádost (Paging on Demand)
34
tento termín je zaveden firmou Intel, jiné názvy pro totéž jsou logický (např. v [PLAS91]) či virtuální
adresový prostor
35
některá literatura hovoří též o podadresáři či adresáři 2. úrovně
36
pokud je stránkování vypnuto (v reálném režimu či vynulováním bitu PG v registru CR0), je mapování
stránek do rámců triviální (fyzická i lineární adresa si jsou rovny)
33
XXVI
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci
Lineární adresa virtuálního adresového prostoru
Index do adresáře Index do podadresáře Ofset ve stránce
31
22 21
12 11
0
Adresář stránek
0. položka
1. položka
2. položka
…
…
…
1023. položka
Tabulka stránek
0. položka
1. položka
2. položka
…
…
…
1023. položka
Registr CR3
Fyzická adresa odkazovaného objektu
31
Fyzická paměť
Velikost
stránky je
4096 bytů
AVL
0
0
D
A
0
0
U W P
12 11 9
8
7
6
5
4
3
2
1 0
obrázek 8 – převod lineární adresy na fyzickou pomocí mechanismu stránkování
Formát položek obou úrovní (adresáře i tabulek) je stejný jako formát registru CR3,
význam bitů je pak následující:
AVL (Available) – volně k použití systémem
D (Dirty) – nastaven na 1 při každém zápisu do stránky
A (Accessed) – nastaven na 1 při každém přístupu (čtení/zápis) do stránky (adresáře)
U (User Accessible) – pokud U = 1, proces s CPL = 3 smí k této stránce přistupovat
(procesy s CPL < 3 mohou přistupovat bez omezení)
W (Writeable) – pokud W = 1, proces s CPL = 3 smí do této stránky zapisovat (procesy
s CPL < 3 mohou zapisovat bez omezení)
P (Present) – pokud P = 0, obsah stránky není ve fyzické paměti a pokus o přístup do ní
vyvolá výjimku číslo 14
Do jednoho rámce fyzické paměti může být mapováno více stránek logické paměti.
V okamžiku zapnutí mechanismu stránkování nemusejí být mapovány všechny stránky. Pokud
procesor narazí na instrukci pracující s adresou, jež leží v nenamapovaném prostoru, vyhodí se
výjimka číslo 14, která chybu ošetří (výjimka typicky provede mapování přistupované stránky
do některého rámce fyzické paměti37). V registru CR2 je lineární adresa instrukce, která výjimku
způsobila. Výjimka 14 se volá zároveň při porušení ochrany přístupu ke stránce u procesů
s CPL = 3 (a U či W = 0).
Spouštění reálného kódu v rámci chráněného režimu
Chráněný režim práce přinesl s sebou kvalitní způsob ochrany paměti i periférií i možnost
virtualizace paměti. Díky tomu se stal procesor Intel 80386 platformou pro nejrozšířenější
operační systémy osobních počítačů – Windows 95/98/NT/2000 a Linux. Z důvodu množství již
hotového software psaného pro reálný mód procesoru je žádoucí, aby bylo takový kód možno
37
vybere, případně i uvolní, určitý rámec paměti a do ní načte obsah přistupované stránky
XXVII
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci
spouštět i v rámci chráněného režimu procesoru. Intel 80386 zavádí možnost označit
zpracovávanou úlohu (nastavením bitu VM v příznakovém registru38) jako virtuální39,
pro takovou úlohu pak platí pravidla adresace reálného módu, zůstává však zapnuta ochrana
paměti i periférií i případný mechanismus stránkování (je tak možno spouštět několik virtuálních
úloh současně).
CPL virtuální úlohy je vždy rovno 3, přístup k perifériím se neřídí hodnotou v IOPL
(implicitně je povolen přístup ke všem portům), některé porty je možno zakázat v neformátované
části TSS. Je-li IOPL rovno 3, jsou povoleny instrukce CLI, STI, INT i IRET, ovšem INT (i
asynchronní přerušení) má stejnou obsluhu, jako v chráněném režimu. Použití privilegované
instrukce vyvolá výjimku číslo 6 či 13. Pokud je IOPL < 3, výjimku 13 vyvolá použití instrukcí
CLI, STI, INT, IRET i POPF (systém musí emulovat). Lze libovolně číst do i zapisovat
ze segmentových registrů (i CS).
B.6. Procesor Intel 80486
Intel 80486 integruje vyrovnávací paměť o velikosti 8kB do procesoru, určité verze
(označené písmeny DX) mají již v sobě zabudován matematický koprocesor40 (FPU – Floating
Point Unit) pro práci s reálnými čísly (dosud byl koprocesor dodáván zvlášť).
Přidává nové instrukce pro práci s vyrovnávací pamětí (INVD, WBINVD), instrukci
k zneplatnění načtené stránky (INVLPG), instrukci pro převrácení pořadí bytů v registru (BSWAP),
instrukci pro porovnání a přehození dvou operandů (CMPXCHG) a instrukci pro pohození dvou
operandů a jejich sečtení (XADD).
Využívá další bity v registru CR0, CR3 (i v položkách adresářů stránkování) a EFLAGS,
především opět v souvislosti s vyrovnávací pamětí. Jinak žádné větší změny s ohledem
na programové použití procesor neobsahuje.
B.7. Procesor Intel Pentium
Instrukce
Procesor Intel Pentium přidává jen minimum nových instrukcí.
Instrukce CMPXCHG8B porovnává obsah dvojice registrů EDX:EAX s 64-bitovým operandem
uloženým v paměti, pokud jsou si rovny, uloží na uvedenou adresu paměti obsah registrů
ECX:EBX, pokud si nejsou rovny, uloží naopak obsah paměti do registrů EDX:EAX. Dle výsledku
porovnání zároveň nastaví či vynuluje příznak ZF v příznakovém registru.
Instrukce RDMSR (Read from Model Specific Register) načte do registrů EDX:EAX hodnotu
některého 64-bitového specifického registru (MSR – Model Specific Registr). Registr ECX musí
obsahovat číslo registru MSR. Specifické registry se vážou na konkrétní typ procesoru a nemusejí
být v dalších typech podporovány. Instrukce WRMSR zapíše do daného registru MSR (určeného ECX)
hodnotu z EDX:EAX. Tato instrukce serializuje spouštění instrukcí.
Instrukce RDTSC (Read Time-Stamp Counter) načte do EDX:EAX hodnotu vnitřního čítače
inkrementovaného každý hodinový cyklus procesoru. Bitem číslo 2 (TSD – Time Stamp Disable)
38
přepnutí se provede skokem na TSS segment, kde je v registru EFLAGS nastaven bit VM
někdy se též nesprávně označuje jako virtuální mód práce procesoru, jde však pouze o jednu úlohu
v rámci chráněného režimu práce
40
k výrobě dvou typů čipů s integrovaným FPU a bez něj zvolila firma Intel netradiční technické řešení –
ve skutečnosti mají všechny procesory integrován FPU, levnější variantě (bez FPU) je však uměle
odříznut (jeho výroba je tedy paradoxně nákladnější)
39
XXVIII
Dodatek B - Procesory rodiny IA-32 a jejich předchůdci
v registru CR4 lze zakázat použití této instrukce v procesech s CPL větší než 0 i v reálném režimu
práce. Instrukce neserializuje spuštění předchozích instrukcí.
Instrukce RSM (Resume from System Management Mode) byla již implementována
na procesorech Intel 80386SL a Intel 80486SL, které byly používány v energeticky nenáročných
systémech (přenosné počítače).
B.8. Procesor Intel Pentium MMX
Dalším, firmou Intel zveřejněným procesorem, je Intel Pentium MMX. Rozšíření a změny
oproti předešlé verzi jsou minimální, největší rozdíl je v přidání nové sady instrukcí
pro zpracování multimédií (MMX – Multi Media eXtension), která zde však není popisována.
B.9. Procesor Intel Pentium Pro
Procesor Intel Pentium Pro byl zveřejněn krátkou dobu po procesoru Intel Pentium MMX,
ale nedá se mluvit o jeho přímém následníkovi. Intel Pentium Pro má zcela jinou vnitřní
architekturu41 než předešlé procesory, a neobsahuje sadu multimediálních instrukcí.
B.10. Procesory Intel Pentium II, III, IV
Procesory Intel Pentium II a III vycházejí z obou předešlých verzí – procesoru Intel
Pentium MMX i Intel Pentium Pro. Mají všechny parametry shodné, či lepší než oba uvedené
procesory, vnitřní architekturu mají stejnou jako Intel Pentium Pro. Procesor Intel Pentium IV byl
zveřejněn teprve v průběhu psaní této práce a není zde popisován.
B.11. Konkurenční procesory
Komerční úspěch procesorů firmy Intel znamenal vytvoření poměrně rozsáhlé konkurence
procesorů lišících se sice vnitřní architekturou, ovšem programově kompatibilních s procesory
firmy Intel. Jedním z nejúspěšnějších konkurentů jsou procesory K5, K6 a K7 firmy AMD42, viz
[AMD99]. Procesory AMD se podobně jako procesory Intel skládají z několika paralelních
jednotek umožňujících spekulativní spouštění kódu, predikci skoků, přejmenování registrů i
změnu pořadí vykonávaní instrukcí.
41
tzv. P6 architektura
procesory firmy AMD ve výkonnosti dokonce předstihly procesory Intel, jsou schopny běžet na vyšších
hodinových kmitočtech a i při stejné frekvenci dávají lepší výsledky
42
XXIX
Dodatek C - Slovníček pojmů
Dodatek C - Slovníček pojmů
Zde je zavedeno několik obecných pojmů, se kterými se lze setkat v textu. Význam
dalších pojmů lze nalézt v [JIRO00], [PLAS91], [CRVV92] a [CADA91].
Analýza kódu
Algoritmus, který označí kódové oblasti v binárním souboru a rozpozná instrukce větvení
včetně jejich cílových adres.
Analýza kódu částečná
Analýza, jejímž výstupem je nekompletní informace o binárním kódu. Týká se to
především nerozpoznání všech oblastí s kódem a neoznačení všech cílových adres instrukcí
větvení.
Analýza kódu úplná
Analýza, jež rozpozná všechny cílové adresy všech instrukcí větvení v programu a
správně označí všechny oblasti se spustitelným kódem.
Architektura P5
Architektura procesorů Intel Pentium a Intel Pentium MMX. Vyznačuje se proudovým
zpracováním instrukcí, procesor implementuje dvě nezávislé linky proudového zpracování, které
umožňují v mnoha případech vykonat dvě instrukce v jednom hodinovém cyklu procesoru.
Architektura P6
Architektura procesorů Intel Pentium Pro, Intel Pentium II a Intel Pentium III. Zpracování
instrukcí procesoru je založeno na jejich překladu do mikrooperací, které jsou následně
vykonávány procesorem. Jedna instrukce procesoru se skládá z jedné či více mikrooperací. Tento
typ architektury se též označuje jako post-RISC.
BIOS (Basic Input Output System)
Souhrn základních služeb poskytovaných OS k ovládání hardware počítače.
Bod programu vstupní
Adresa, kde začíná chod programu či některé jeho funkce. Po natažení souboru do paměti,
je na tuto adresu předáno řízení, odsud se pokračuje do všech podprocedur a částí programu.
V případě aplikací je to jediná adresa, v případě sdílených knihoven je to začátek každé
exportované funkce.
Duplikace kódu
Zápis stejného kódu ve dvou verzích do jednoho spustitelného souboru. První verze
obsahuje původní (až na cílové adresy některých instrukcí větvení) nepozměněný kód, druhá
verze obsahuje zoptimalizovaný rozpoznaný kód. Původní kód navíc leží na původních adresách.
Duplikace kódu umožňuje optimalizaci částečně analyzovaného kódu bez obavy poškození
funkčnosti programu.
Ekvivalence operandů
Dva operandy, jejichž záměnou v instrukci se nezmění výsledek provedení instrukce.
Ekvivalence nemusí být absolutní, bývá vztažena na statický kontext instrukce.
Formát souboru
Seznam informací, které jsou v souboru zapsány a konkrétní způsob jejich zakódování.
Funkce exportovaná
Kód, jenž mohou využívat okolní aplikace. Typicky bývají exportované funkce
v souborech se sdílenými knihovnami, ovšem i aplikace mohou exportovat funkce. Adresa
exportované funkce je jedním ze vstupních bodů binárního kódu uplatněných při jeho analýze.
XXX
Dodatek C - Slovníček pojmů
Funkce importovaná
Kód, který je importován ze sdílené knihovny a bývá využíván binárním kódem.
IA-16, IA-32, IA-64
Označení procesorů firmy Intel s 16-bitovou, 32-bitovou a 64-bitovou architekturou. Tyto
procesory jsou zpětně kompatibilní.
Idiom
Skupina instrukcí strojového kódu, která je generována překladačem při překladu daného
sémantického prvku (např. konstrukt switch). Idiomy mohou být rozpoznávány při analýze
binárního kódu. Formát idiomu je závislý na typu procesoru a použitého překladače.
Informace relokační
Seznam adres v binárním souboru, které obsahují absolutní hodnotu jako ukazatel jinam
do tohoto binárního souboru. Operační systém, který zavede aplikaci na jinou adresu, než
na jakou byla spojována, musí přepočítat všechny tyto absolutní odkazy v aplikaci.
Instrukce
Řetězec symbolů, které při interpretaci procesorem způsobí jednoznačnou a definovanou
změnu stavu stroje. Každá instrukce se potom skládá ze dvou částí – operačního kódu, který
specifikuje, jaká změna stavu bude docílena provedením této instrukce a specifikace
identifikátorů v adresovém prostoru počítače, které mají být při změně stavu použity jako
parametry.
Instrukce redundantní
Instrukce, jejímž odstraněním z kódu se nezmění funkčnost programu.
Instrukce ve svazku dřívější
Pokud jsou instrukce I1 a I2 součástí stejného svazku instrukcí, je I1 dřívější instrukce
právě tehdy, když adresa instrukce I1 je numericky menší než adresa instrukce I2.
Instrukce ve svazku pozdější
Pokud jsou instrukce I1 a I2 součástí stejného svazku instrukcí, je I2 pozdější instrukce
právě tehdy, když adresa instrukce I1 je numericky menší než adresa instrukce I2.
Instrukce větvení
Instrukce, která mění tok programu. Vykonáním této instrukce program nepokračuje
na následující instrukci, ale na cílové adrese, jež je operandem instrukce větvení. Větvení mohou
být podmíněná (Conditional), nepodmíněná (Unconditional), přímá (Direct) či nepřímá
(Indirect). Nepodmíněná instrukce větvení se provede vždy, podmíněná instrukce jen tehdy, zdali je splněna podmínka specifikovaná v instrukci. Přímé větvení má jako operand uvedenu
konstantní adresu, jež je známa již při vytváření binárního souboru, adresa nepřímého větvení se
spočte až za běhu programu. Cílová adresa může být relativní vzhledem k adrese instrukce
větvení nebo absolutní. Větvení může tok programu změnit dočasně (s návratem, tzv. volání
podprocedury) nebo trvale (bez návratu, tzv. skok či návrat z podprocedury).
JVM (Java Virtual Machine)
Virtuální stroj Javy. Procesorová architektura navržená k vykonávání bezpečných a
přenositelných binárních kódů malé velikosti. Není implementována v čipu a musí se emulovat
nad jinými platformami.
XXXI
Dodatek C - Slovníček pojmů
Kód binární
Soubor konkrétního formátu obsahující spustitelný (cílový) či spojovatelný kód a data,
které jsou již zakódovány nativním způsobem konkrétního procesoru. Pro jejich zpracování
procesorem není třeba kód ani data speciálně upravovat, kromě počáteční fáze, kdy je provedeno
mapování částí souboru na virtuální adresy, jsou relokovány absolutní hodnoty ukazující
do souboru, a cílové adresy instrukcí větvení volající importované funkce jsou dynamicky
spojeny se vstupními adresami těchto funkcí.
Kód instrukce operační
Binární reprezentace instrukce v procesoru.
Kód optimalizovaný
Výstupní soubor algoritmu optimalizace.
Kód původní
Vstupní soubor pro algoritmy analýzy a optimalizace.
Kódování varianty instrukce
Operační kód instrukce včetně zakódovaných konkrétních použitých operandů.
Kolize operandů
Kolize operandů se týká instrukcí, které operandy používají. Dva operandy spolu kolidují,
pokud je alespoň jeden z nich instrukcí zapisován a pokud zápis do operandu ovlivňuje hodnotu
druhého operandu.
Kontext instrukce statický
Konkrétní varianty instrukcí, jež předcházejí instrukci nebo jež následují za instrukcí.
Kontext procesoru
Hodnoty registrů, příznaků apod. v danou chvíli.
Kopie svazku optimalizovaná
Svazek instrukcí, jehož funkčnost je stejná jako příslušný původní svazek instrukcí, může
se však skládat z jiných instrukcí či jiných variant instrukcí.
Oblast se spustitelným kódem
Přesně vymezené místo v souboru, jež obsahuje pouze skutečně spustitelný kód. V praxi
je to obsah sekce se spustitelným kódem, ze které jsou odstraněny nespustitelná data (statické
texty apod.).
Ofset
Adresa nebo ukazatel, kde se nejedná o absolutní adresu, ale o adresu „uvnitř“ nějakého
objektu vyšší úrovně, který má vlastní adresu a sečtením obou se dostane teprve absolutní adresa
požadovaného byte. Podobně u mikroprocesorů řady Intel 80x86 se používá pojem ofset
pro adresu uvnitř segmentu; teprve součet ofsetu a adresy počátku segmentu dá absolutní adresu.
Operand instrukce
Parametr, se kterým instrukce pracuje. Operand může být zadán explicitně či implicitně,
instrukce jej může číst, zapisovat či modifikovat (číst i zapisovat). Operandem může být obecně
libovolný registr (či jen jeho část), adresa paměti či brána vstupně/výstupního zařízení. Jedna
instrukce může mít několik operandů.
Operand instrukce nepřímý
Nekonstantní operand instrukce (registr, adresa v paměti apod.).
Operand instrukce přímý
Konstantní operand instrukce. Hodnota přímého operandu je součástí operačního kódu
instrukce a v různých variantách stejné instrukce může být kódována jiným počtem bitů, na IA32 typicky 8 či 32 bity, na jiných procesorech však i jiným počtem (10, 13, 22 bitů).
XXXII
Dodatek C - Slovníček pojmů
Optimalita operandu
Vlastnost operandu, která určuje optimálnost jeho použití. Lze definovat relaci, která
pro každé dva různé ekvivalentní operandy určí, který z nich je optimálnější.
Optimalizace binárního kódu
Optimalizace binárního kódu je algoritmus, který aplikováním na tento kód produkuje
“vylepšený” kód a to buď ve smyslu vyšší rychlosti spuštěného kódu, a/nebo ve smyslu nižší
velikosti zabíraného prostoru (velikost binárního souboru). Zásahy do kódu nesmějí mít vliv
na funkčnost kódu.
Paměť asociativní
Paměť umožňující zaznamenat blok dat na adresu, která není specifikována při ukládání
dat, ale je vybrána jiným kritériem. Např. algoritmus LRU může vybrat libovolnou nejdéle
nepoužitou adresu. Rychlost čtení dat z asociativní paměti zůstává konstantní.
Paměť částečně asociativní
Částečně asociativní paměť stupně N neukládá zapisovaná data na libovolnou adresu,
výběr adresy je omezen velikostí stupně, paměť vybírá z N možných adres.
Prodlení
Pozdržení vykonání instrukce o jeden či několik cyklů z důvodu, který souvisí s typem
instrukce a jejím statickým kontextem.
Proměnná rozskoková
Proměnná obsahující vhodně upravenou hodnotu výrazu používaného k větvení
v konstruktu switch. Hodnota této proměnné bývá indexem do tabulky cílových adres
jednotlivých větví konstruktu switch.
Registry obecné
Registry používané k většině či ke všem aritmeticko-logickým operací procesoru.
Na procesorech IA-16 jsou obecné registry AX, BX, CX, DX, SI, DI, SP a BP (a jejich menší části),
na procesorech IA-32 to jsou navíc registry EAX, EBX, ECX, EDX, ESI, EDI, ESP a EBP.
Rozsah operandu
Rozdíl mezi nejnižší a nejvyšší hodnotou zapsatelnou do operandu. Typicky je to 8, 16
nebo 32 bitů.
Segmentace
Způsob virtualizace paměti založený na rozdělení paměti do menších částí, kdy každá část
může být jinak veliká (limit segmentu) a začínat na libovolné adrese (báze segmentu).
Sekce
Vnitřní část binárního souboru, jež obsahuje data (i ve smyslu spustitelného kódu). Sekci
se dají přiřadit určité atributy (spustitelná data, pouze pro čtení apod.), jež se vztahují na všechna
data v sekci. Celá sekce se obvykle mapuje na určitý souvislý úsek paměti.
Sekce datová
Sekce, která obsahuje data. Dle atributu sekce může jít o data určená pouze ke čtení
či přepisovatelná data.
Sekce kódová
Sekce, která má nastavený atribut označující přítomnost spustitelného kódu. V praxi však
obsahuje i další (nepřepisovatelná) data, např. statické lokální texty či pomocné tabulky mající
nějakou souvislost s blízkým kódem. Obvykle obsahuje soubor jedinou sekci se spustitelným
kódem.
XXXIII
Dodatek C - Slovníček pojmů
Stránkování
Způsob virtualizace paměti založený na rozdělení lineárního prostoru do stejně velkých
souvislých úseků (stránek), rozdělení fyzické paměti do bloků téže velikosti (rámce) a stanovení
způsobu mapování stránek do rámců.
Svazek instrukcí
Posloupnost instrukcí, jež neleží na adrese některé instrukce větvení, kromě první
instrukce posloupnosti. Platí, že existuje instrukce větvení, jejíž operand směřuje na první
instrukci posloupnosti. Velikost svazku může označovat počet instrukcí ve svazku nebo počet
bytů, jež vznikne součtem velikostí všech instrukcí ve svazku.
Varianta instrukce
Konkrétní instrukce spojená s použitím konkrétního jejího operandu.
Virtualizace paměti
Mechanismus vytvoření logického (virtuálního) adresového prostoru a způsob přepočtu
(mapování) adres mezi logickým a fyzickým adresovým prostorem.
Vztah dvou instrukcí k operandu
Pokud instrukce I1 a I2 jsou ve stejném svazku a obě přistupují k operandu O, pak jsou
ve vzájemném vztahu k operandu O. Instrukce mohou být vždy v právě v jednom z následujících
čtyř vztahů – R/R, W/R, R/W a W/W (Read/Write). Za předpokladu, že je I1 dřívější instrukce
než I2, jsou instrukce I1 a I2 ve vztahu R/R, pokud obě instrukce čtou operand O; jsou ve vztahu
R/W, pokud instrukce I1 čte operand O a instrukce I2 jej zapisuje; jsou ve vztahu W/R, pokud
instrukce I1 zapisuje operand O a instrukce I2 jej čte; a jsou ve vztahu W/W, pokud operand O
obě instrukce zapisují.
XXXIV