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