Překladače na platformě IBM PC: od assembleru k C

13. 5. 2025
Doba čtení: 38 minut

Sdílet

Autor: Depositphotos
Prozatím jsme se zaměřili na assembler. Proč se však více nepoužívaly překladače vyšších programovacích jazyků? Dnes se na tuto otázku pokusíme částečně odpovědět otestováním několika dobových překladačů jazyka C.

Obsah

1. Překladače na platformě IBM PC: od assembleru k C

2. Programovací jazyk C

3. Lattice C

4. Aztec C

5. Zortech C

6. Turbo C

7. Borland C++

8. Watcom C/C++

9. Jak kvalitní byly překladače céčka pro IBM PC?

10. Příklad první: funkce pro součet dvou celých čísel

11. Výsledky vygenerované překladačem Borland C++

12. Výsledky vygenerované překladačem Watcom C/C++

13. Příklad druhý: vyplnění paměťového bloku

14. Výsledky vygenerované překladačem Borland C++

15. Výsledky vygenerované překladačem Watcom C/C++

16. Příklad třetí: nalezení prvku v poli s maximální hodnotou

17. Výsledky vygenerované překladačem Borland C++

18. Výsledky vygenerované překladačem Watcom C/C++

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Překladače na platformě IBM PC: od assembleru k C

V seriálu o vývoji her a grafických dem pro platformu PC jsme prozatím pro tvorbu příkladů používali assembler. V prvních přibližně deseti letech existence platformy IBM PC se skutečně jednalo o primární jazyk využívaný pro tvorbu profesionálních aplikací. Příkladem takové aplikace může být textový procesor WordStar nebo tabulkový procesor VisiCalc (předchůdce Lotusu 1–2–3 a vlastně i Excelu). A v assembleru byla vytvořena i celá řada her. Proč se však nepoužívaly překladače vyšších programovacích jazyků? Takové překladače pochopitelně existovaly, ostatně první překladač céčka byl na IBM PC portován již rok po vzniku této platformy. Ovšem kvalita výsledného strojového kódu nebyla příliš dobrá, protože tyto překladače většinou neprováděly velké optimalizace. A nutno říci, že i samotná mikroprocesorová architektura 80×86 tvůrcům překladačů nebyla příliš nápomocná (na rozdíl od RISCových architektur).

Obrázek 1: Dobová reklama na textový editor WordStar.

V dnešním článku se nejdříve zmíníme o některých překladačích jazyka C, které byly dostupné pro platformu IBM PC a posléze si na trojici příkladů vyzkoušíme, jak kvalitní nebo nekvalitní vlastně překlad ve skutečnosti byl a proč programátoři poměrně dlouhou dobu zůstávali u assembleru.

2. Programovací jazyk C

Jedním z nejpopulárnějších v současnosti používaných programovacích jazyků je stále (což je mimochodem zajímavé a hodně to vypovídá o realitě v IT) programovací jazyk C navržený Dennisem Ritchiem. Historie céčka je dobře známá: jazyk C se postupně vyvinul z programovacích jazyků BPCL (autor Martin Richards, 1966) a B (autor Ken Thompson, 1970) až do současné podoby, která byla standardizována v několika normách, z nichž nejznámější je pravděpodobně stále ještě ISO/IEC 9899:1999 známá pod zkráceným označením C99. Následovala specifikace C11 neboli ISO/IEC 9899:2011, C17 neboli ISO/IEC 9899:2018 a konečně C23 neboli ISO/IEC 9899:2024. Starší, dodnes v některých případech stále ještě používaný standard, se jmenuje ISO 9899:1990. Tento starší standard je prakticky shodný (až na jiné číslování jednotlivých paragrafů) s normou ANSI C (ANSI X3.159–1989 „Programming Language C“) a zkráceně se označuje C89 či méně často C90 (taktéž jen ANSI/ISO C).

Obrázek 2: Přebal druhého vydání slavné knihy „The C Programming Language“ (Kerningan, Ritchie)

Programovací jazyk C je i přes absenci některých důležitých vlastností (například mu chybí automatický správce paměti či podpora silného typování a práce s objekty, výjimkami, uzávěry, pattern matchingem atd.) využívaný jak pro tvorbu open source aplikací, tak i v čistě komerční oblasti – nejedná se jen o vývoj aplikací pro desktopy a servery, ale i pro mikrořadiče či digitální signálové procesory (DSP). Céčko je mnohdy využíváno i ve funkci cílového jazyka, do něhož se překládají (transpilují) programy zapsané v některých vyšších programovacích jazycích – vývojáři, kteří překladače těchto jazyků vytváří, se tak nemusí starat například o nízkoúrovňové optimalizace, protože je za ně již naprogramovali vývojáři překladače céčka. Z historického hlediska, které nás dnes do jisté míry zajímá, je poměrně typické, že právě tímto způsobem vznikla první verze jazyka C++ (nástroj Cpre), i když moderní překladače C++ jsou již řešeny od C odděleně.

To však není zdaleka vše, protože programovací jazyk C je dodnes důležitý i z toho důvodu, že jak rozhraní jader některých operačních systémů (Linux, Microsoft Windows i dalších systémů), tak rozhraní systémových knihoven bylo navrženo s ohledem na jmenné konvence céčka i s ohledem na možnosti jeho linkeru (a ostatní jazyky toto rozhraní s většími či menšími problémy dodržují).

Nás však dnes budou zajímat především starší překladače určené pro operační systém (MS) DOS a tedy pro platformu IBM PC. Těchto překladačů vznikla celá řada a zajímavé je, že některé z nich vlastně vznikly portací překladače z jiných platforem, ať již se jednalo o osmibitové mikroprocesory na straně jedné či o Unixové systémy na druhé straně výkonnostního spektra.

3. Lattice C

Pravděpodobně prvním překladačem programovacího jazyka C, který byl vytvořen pro platformu IBM PC, byl Lattice C pocházející z roku 1982 (samotné PC přitom začalo být nabízeno jen o rok dříve). Tento překladač byl později převeden i na Amigu (a byl zde poměrně populární) a dále se rozšířil i na minipočítače a mainframy společnosti IBM. Jeho hardwarové nároky byly dnešním pohledem směšně nízké: PC s původním mikroprocesorem Intel 8088 taktovaným na 4,77 MHz, paměť RAM o kapacitě 256kB (původní PC ovšem mohlo mít i jen 16kB RAM!) a disketová mechanika.

Zajímavé bylo, že společnost Microsoft překladač Lattice C nabízela pod svým názvem MSC (Microsoft C) (i když autorem byla firma Lifeboat Associates) a teprve verze MSC 4.0 byla skutečně vytvořena přímo programátory z Microsoftu. Lattice C byl využíván i při portaci aplikací z operačního systému CP/M na systém DOS. Dnes je však poměrně těžké odhadnout, kolik zdrojového kódu bylo skutečně napsáno v céčku a kolik kódu vzniklo transformací assembleru. Jedním z nedostatků Lattice C byla jeho vysoká cena 500 dolarů. To později zjednodušilo přístup na trh s překladači takovým firmám, jako je Borland apod., jejichž cenová politika byla zcela opačná.

Obrázek 3: Lattice C.

4. Aztec C

Dalším kdysi známým překladačem programovacího jazyka C, který byl kromě platformy IBM PC dostupný mj. i pro osobní mikropočítače Amiga, byl překladač nazvaný Aztec C společnosti Manx Software Systems. Jednalo se o ve své době velmi úspěšný překladač, jenž existoval jak ve verzi pro osmibitové mikroprocesory (MOS 6502, Zilog Z-80), tak i pro mikroprocesory 16bitové (včetně Intelu 8088) a 32bitové (řada Motorola 68000). Tento překladač byl velmi úspěšný právě na Amize, kde byl používán, společně s výše zmíněným Lattice C, prakticky až do faktického zániku této platformy.

Ovšem na druhé straně na platformě IBM PC jeho sláva netrvala dlouho, především z toho důvodu, že firma Microsoft považovala segment překladačů za poměrně důležitý a snažila se vytlačit jakoukoli konkurenci z trhu (i když ve skutečnosti v té době ještě stále neměla vlastní céčkový překladač). Autorem Aztec C, jímž byla společnost Manx Software Systems, se navíc postupně zmenšoval počet platforem, na něž bylo možné překladač prodávat a přechod na podporu vestavěných systémů již přišel dosti pozdě.

Obrázek 4: Logo překladačů Aztec C.

5. Zortech C

Za vývojem překladače Zortech C stála společnost Digital Mars. Tato firma vytvořila a prodávala (a vlastně i doposud prodává) překladače jazyků C, C++ a D. V dnešním článku se ovšem zaměříme pouze na klasické céčko, resp. překladače dostupné pro platformu IBM PC. Původní překladač jazyka C od společnosti Digial Mars se jmenoval Small-C, ovšem nepokrýval všechny vlastnosti jazyka C. První překladač skutečného céčka vznikl až v roce 1987 a jmenoval se Datalight C. Poté postupným vývojem vznikly další varianty překladače céčka a zajímavé je, že se měnil i jejich název. Po Datalight C začal být nabízen Zorland C (reference na konkurenční Borland?), Zortech C a Digital Mars C/C++. Pravděpodobně nejvíce rozšířený a nejznámější byla právě varianta nazvaná Zortech C (existoval i Zortech C++). Ten v benchmarcích porážel konkurenci – Microsoft C 5.1 i Watcom C 6.5 (a pochopitelně i překladač Turbo C a Borland C++).

6. Turbo C

Dalším známým překladačem programovacího jazyka C pro platformu IBM PC je překladač, nad kterým bylo postaveno integrované vývojové prostředí Turbo C. Tento produkt společnosti Borland (která je známá především díky svému Turbo Pascalu a později Delphi) byl vydán v roce 1987. Zajímavé je, že tato první verze vyžadovala ke svému běhu pouze 384kB RAM a obsahovala podporu pro zápis subrutin (podprogramů) v assembleru, který měl navíc přístup ke všem céčkovským symbolům. Navíc díky tomu, že překladač neprováděl prakticky žádné optimalizace, byla tvorba subrutin v assembleru snadná (to dnes již není možné – je totiž nutné překladači napovídat).

Obrázek 5: Uživatelské rozhraní Turbo Pascalu 5.5. Vidíme zde klasické „modré“ IDE.

O rok později, tj. v roce 1988, byla vydána verze Turbo C 1.5. Ta již poněkud nabobtnala – byla dodávána na pěti disketách s kapacitou 360kB (což je ovšem z dnešního pohledu prakticky „nic“). A konečně taktéž v roce 1988 byla ještě vydána verze 2.0, která byla vybavena známým „modrým IDE“, které se stalo pro produkty společnosti Borland typické. Tato verze obsahovala i podporu pro Turbo Debugger a Turbo Assembler.

Obrázek 6: Dialog se základními informacemi o IDE Turbo Pascal 7.0.

7. Borland C++

V roce 1990 byl vydán Turbo C++, čímž vlastně skončila historie Turbo C zmíněného v předchozí kapitole. Jak již název napovídá, jednalo se o IDE s překladačem jazyka C++, který ovšem dokázal pracovat i s čistým céčkem. I tento produkt se postupně vyvíjel. Verze 2.0 se již jmenovala Borland C++; právě tuto verzi z roku 1991 použijeme v demonstračních příkladech.

Prekladace pro IBM PC a DOS

Obrázek 7: Integrované vývojové prostředí Borland C++ verze 2.0.

Zajímavá je verze 3.0 (taktéž z roku 1991), která podporovala překlad aplikací pro Microsoft Windows. V roce 1992 byla vydána verze 3.1 s podporou dvou frameworků pro tvorbu aplikací s grafickým uživatelským rozhraním: OWL (Windows) a Turbovision (DOS). Historie Borland C++ pro operační systém DOS se tímto uzavírá, protože verze 4.0 z roku 1993 je již určena pro Windows 3.x.

Prekladace pro IBM PC a DOS

Obrázek 8: Dialog s nastavením optimalizací překladače je poměrně jednoduchý. Tento překladač ostatně více optimalizací ani nepodporoval.

8. Watcom C/C++

Dalším důležitým překladačem jazyka C pro platformu IBM PC je Watcom C/C++, který byl v roce 1988 vydán kanadskou společností Watcom International Corporation. I tato společnost, podobně jako výše zmíněný Borland nebo Digital Mars, produkovala různá vývojové nástroje. Prvním z nich byl Waterloo BASIC, jenž byl portován na různé počítače (IBM 370, IBM 4300 atd.). Později byly vydány i další vývojové nástroje, například Watcom APL, Watcom GKS, Watcom COBOL, Watcom FORTRAN, Watcom Pascal a Waterloo 6809 Assembler (zde je zřejmé, pro jaké mikroprocesory byl určen). Ovšem nás nejvíce zajímá právě Watcom C/C++, protože tento překladač je známý podporou různých optimalizací výsledného kódu. V benchmarcích bez problémů porážel překladače od Borlandu a většinou i od Microsoftu.

Prekladace pro IBM PC a DOS

Obrázek 9: Integrované vývojové prostředí Open Watcomu.

Watcom C/C++ sice již není vyvíjen komerčně, ovšem existuje Open Watcom C/C++, který je možné si nainstalovat a vyzkoušet; kromě moderních systémů i v emulátorech PC a DOSu.

Prekladace pro IBM PC a DOS

Obrázek 10: Watcom nezapře své kořeny v prostředí odlišném od IBM PC. Programy se v něm tvořily v editoru vi, resp. v jeho upravené variantě.

Prekladace pro IBM PC a DOS

Obrázek 11: Nastavení editoru vi pro Watcom.

9. Jak kvalitní byly překladače céčka pro IBM PC?

Jak již bylo napsáno v úvodu dnešního článku, používal se na platformě IBM PC pro tvorbu profesionálního software poměrně dlouho assembler (tedy jazyk symbolických instrukcí). Bylo tomu tak z toho důvodu, že překladače jazyka C zpočátku nebyly příliš kvalitní, minimálně ne z pohledu optimalizací (a některé překladače byly navíc chybové nebo neodpovídaly normě C, to je však jiné téma). Jak uvidíme v demonstračních příkladech uvedených v navazujících kapitolách, prováděly některé céčkové překladače poněkud šablonovitý překlad; ostatně se o programovacím jazyku C někdy s nadsázkou říká, že je to „přenositelný assembler“ [1].

Prekladace pro IBM PC a DOS

Obrázek 12: Přepínače překladače Watcom C/C++, mezi nimiž nalezneme i několik přepínačů ovlivňujících optimalizace.

Teprve později začaly být překladače céčka vybavovány více či méně kvalitními optimalizacemi výsledného strojového kódu. To však vyžadovalo větší nároky na operační paměť, delší dobu překladu a horší podporu pro ladění (resp. krokování). Proto je optimalizaci prakticky nutné explicitně povolit a nastavit její vlastnosti.

10. Příklad první: funkce pro součet dvou celých čísel

První demonstrační příklad, na kterém si ověříme kvalitu nebo naopak nekvalitu překladačů jazyka C, je vlastně triviální. Jedná se o funkci se dvěma parametry typu „celé číslo“ (ať to znamená cokoli), která vrací součet hodnot těchto dvou parametrů. Implementace takové funkce je jednoduchá:

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

Jen pro zajímavost se podívejme, jak dopadne výsledek překladu na moderní architektuře x86–64 s využitím překladačů GCC a Clang.

Překlad s využitím GCC nebo Clang s vypnutými optimalizacemi (výsledky jsou totožné):

add:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     DWORD PTR [rbp-8], esi
        mov     edx, DWORD PTR [rbp-4]
        mov     eax, DWORD PTR [rbp-8]
        add     eax, edx
        pop     rbp
        ret

Překlad s využitím GCC nebo Clang se zapnutými maximálními optimalizacemi (výsledky jsou opět totožné):

add:
        lea     eax, [rdi+rsi]
        ret

Zde stojí za zmínku, že platforma x86–64 má svá specifika. Prvním z nich je, že typ int je 32bitový a druhým je možnost NEpoužívat předávání parametrů přes zásobníkový rámec. Celočíselné hodnoty jsou totiž předávány v registrech RDI, RSI, RDX, RCX, R8 a R9 (v tomto pořadí). Viz AMD64 ABI.

Prekladace pro IBM PC a DOS

Obrázek 13: Nainstalovaný překladač Borland C++ 2.0.

11. Výsledky vygenerované překladačem Borland C++

Překladač Borland C++ nabízí volbu -B, která zajistí, že se zdrojový kód z jazyka C přeloží do assembleru a ve druhé fázi dojde k překladu z assembleru do strojového kódu. Pokud tuto volbu použijeme, bude assemblerovský zdrojový kód obsahovat mj. i naši funkci add, která má ovšem externí symbol _add:

_TEXT   segment byte public 'CODE'
        assume  cs:_TEXT
_add    proc    near
        push    bp
        mov     bp,sp
        mov     ax,word ptr [bp+4]
        add     ax,word ptr [bp+6]
        jmp     short @1@50
@1@50:
        pop     bp
        ret
_add    endp
        ?debug  C E9
_TEXT   ends
Poznámka: povšimněte si typického „prologu“ a „epilogu“ funkce, který slouží k vytvoření a zrušení zásobníkového rámce:
        ; prolog
        push    bp
        mov     bp,sp

a:

        ; epilog
        pop     bp
        ret

Taktéž stojí za povšimnutí zbytečné skoky, které překladač do kódu vkládá a které lze odstranit zapnutím optimalizací:

        jmp     short @1@50
@1@50:

Pro snadnější čtení vygenerovaného assembleru můžeme snadno překladač nastavit tak, aby k externím symbolům nepřidával na začátek podtržítko. Výsledek bude prakticky totožný s předchozím příkladem, ovšem bude se lišit návěští _add od add:

_TEXT   segment byte public 'CODE'
        assume  cs:_TEXT
add     proc    near
        push    bp
        mov     bp,sp
        mov     ax,word ptr [bp+4]
        add     ax,word ptr [bp+6]
        jmp     short @1@50
@1@50:
        pop     bp
        ret
add     endp
        ?debug  C E9
_TEXT   ends

Zajímavější je však přepínač -S, jenž povolí překlad přímo do assembleru (nic dalšího). Nyní bude výsledný kód zkombinován s poznámkami obsahujícími původní céčkovskou funkci:

   ;
   ;    int add(int a, int b) {
   ;
        assume  cs:_TEXT
_add    proc    near
        push    bp
        mov     bp,sp
   ;
   ;        return a+b;
   ;
        mov     ax,word ptr [bp+4]
        add     ax,word ptr [bp+6]
        jmp     short @1@50
@1@50:
   ;
   ;    }
   ;
        pop     bp
        ret
_add    endp

Popř. se zákazem vložení podtržítka do jmen externích symbolů:

   ;
   ;    int add(int a, int b) {
   ;
        assume  cs:_TEXT
add     proc    near
        push    bp
        mov     bp,sp
   ;
   ;        return a+b;
   ;
        mov     ax,word ptr [bp+4]
        add     ax,word ptr [bp+6]
        jmp     short @1@50
@1@50:
   ;
   ;    }
   ;
        pop     bp
        ret
add     endp

Z výsledků je patrné, že hodnoty typu int jsou šestnáctibitové (což v DOSu není překvapující) a taktéž to, že se parametry předávají přes zásobník, tvoří se standardní zásobníkový rámec (stack frame) a návratová hodnota je předána v registur AX. Typická je zejména tvorba zásobníkového rámce kombinací instrukcí:

        push    bp
        mov     bp,sp

s jeho odstraněním na konci:

        pop     bp
        ret

Překladače Borland navíc často generují zbytečné skoky (na další pozici v paměti):

        jmp     short @1@50
@1@50:

Tyto skoky lze ze strojového kódu snadno odstranit – ostatně jedná se o jednu z pouhých tří optimalizací, které tento překladač umožňuje:

   ;
   ;    int add(int a, int b) {
   ;
        assume  cs:_TEXT
add     proc    near
        push    bp
        mov     bp,sp
   ;
   ;        return a+b;
   ;
        mov     ax,word ptr [bp+4]
        add     ax,word ptr [bp+6]
   ;
   ;    }
   ;
        pop     bp
        ret
add     endp
Poznámka: prozatím tedy překladač Borland C++ příliš neoslnil.
Prekladace pro IBM PC a DOS

Obrázek 14: Překlad provedený přímo v integrovaném vývojovém prostředí.

Prekladace pro IBM PC a DOS

Obrázek 15: Nastavení optimalizací při překladu.

12. Výsledky vygenerované překladačem Watcom C

Podívejme se, jak se s překladem funkce add vypořádal známý Watcom C, který byl dlouho považován za nejlépe optimalizující překladač pro platformu IBM PC. Nejdřív si uvedeme výsledek překladu s vypnutými optimalizacemi:

Module: C:\add.c
GROUP: 'DGROUP' CONST,CONST2,_DATA
 
Segment: _TEXT BYTE USE16 0000000B bytes
0000                          add_:
0000    50                        push        ax
0001    B8 02 00                  mov         ax,0x0002
0004    E8 00 00                  call        __STK
0007    58                        pop         ax
0008    01 D0                     add         ax,dx
000A    C3                        ret
 
Routine Size: 11 bytes,    Routine Base: _TEXT + 0000
 
No disassembly errors
 
Segment: CONST WORD USE16 00000000 bytes
 
Segment: CONST2 WORD USE16 00000000 bytes
 
Segment: _DATA WORD USE16 00000000 bytes

Z výsledků je patrné, že typ int je v šestnáctibitovém DOSu taktéž šestnáctibitový. Parametry jsou předávány v registrech AX a DX, výsledek se vrací v registru AX. Navíc se zde explicitně volá subrutina __STK, která kontroluje přetečení zásobníku v závislosti na jeho velikosti předané v registru AX. Výsledný kód tedy není zcela ideální, ale není ani nejhorší možný.

Po zapnutí všech optimalizací ovšem získáme mnohem lepší výsledek. Celá funkce se zkrátila na jediný součet a návrat ze subrutiny:

Module: C:\add.c
GROUP: 'DGROUP' CONST,CONST2,_DATA
 
Segment: _TEXT BYTE USE16 00000003 bytes
0000                          add_:
0000    01 D0                     add         ax,dx
0002    C3                        ret
 
Routine Size: 3 bytes,    Routine Base: _TEXT + 0000
 
No disassembly errors
 
Segment: CONST WORD USE16 00000000 bytes
 
Segment: CONST2 WORD USE16 00000000 bytes
 
Segment: _DATA WORD USE16 00000000 bytes
Poznámka: překlad do objektového kódu byl proveden s využitím WCC, zpětnou transformaci do assembleru zajistila utilita WDIS.

13. Příklad druhý: vyplnění paměťového bloku

Druhý testovací příklad naprogramovaný v jazyku C obsahuje definici funkce, která dokáže vyplnit paměťový blok začínající na zadané adrese a mající určitou (opět zadanou) délku. Tento příklad je upraven do podoby, která by měla do značné míry pomoci překladačům – využívá se zde přístup přes ukazatel (ne přes index), programová smyčka má ten nejjednodušší možný tvar atd. A zápisy se provádí po bajtech:

#include <mem.h>
 
void * memset(void *dest, register int val, register size_t len) {
    register unsigned char *ptr = (unsigned char*)dest;
    while (len-- > 0)
        *ptr++ = val;
    return dest;
}
Poznámka: zde překladačům do jisté míry pomáháme, protože namísto přístupu do paměti přes index (pole) využíváme přímý přístup přes ukazatel.

Ještě než se podíváme na způsob překladu staršími překladači céčka pro IBM PC, podívejme se na překlad s využitím moderních překladačů. Nejdříve GCC bez povolených optimalizací:

memset:
        push    rbp
        mov     rbp, rsp
        push    rbx
        mov     QWORD PTR [rbp-16], rdi
        mov     ecx, esi
        mov     eax, edx
        mov     rbx, QWORD PTR [rbp-16]
        jmp     .L2
.L3:
        mov     rdx, rbx
        lea     rbx, [rdx+1]
        mov     esi, ecx
        mov     BYTE PTR [rdx], sil
.L2:
        mov     edx, eax
        lea     eax, [rdx-1]
        test    edx, edx
        jne     .L3
        mov     rax, QWORD PTR [rbp-16]
        mov     rbx, QWORD PTR [rbp-8]
        leave
        ret

Zde je patrné, že se programová smyčka transformovala poměrně otrockým způsobem.

Překlad s využitím optimalizací je odlišný. Povšimněte si zde registru SIL (dolních osm bitů registru ESI, výchozí registr pro parametr funkce) a instrukce MOVZX, která provádí rozšíření operandu s menší bitovou šířkou do výsledku s větší bitovou šířkou:

memset:
        test    edx, edx
        je      .L2
        mov     edx, edx
        movzx   esi, sil
        jmp     memset
.L2:
        mov     rax, rdi
        ret

Ovšem možná je i „vektorizace“ kódu, která využívá toho, že u delších paměťových bloků lze využít zápis širšího (zarovnaného) slova. Právě zde se ukazuje význam překladačů oproti assembleru, protože ruční tvorba takového kódu je sice možná, ale pracná:

memset:
        mov     rax, rdi
        test    rdx, rdx
        je      .LBB0_8
        cmp     rdx, 4
        jae     .LBB0_3
        mov     r8, rax
.LBB0_14:
        mov     r9, rdx
        jmp     .LBB0_15
.LBB0_3:
        movzx   edi, sil
        cmp     rdx, 32
        jae     .LBB0_9
        xor     ecx, ecx
        jmp     .LBB0_5
.LBB0_9:
        mov     rcx, rdx
        and     rcx, -32
        movd    xmm0, edi
        punpcklbw       xmm0, xmm0
        pshuflw xmm0, xmm0, 0
        pshufd  xmm0, xmm0, 68
        xor     r8d, r8d
.LBB0_10:
        movdqu  xmmword ptr [rax + r8], xmm0
        movdqu  xmmword ptr [rax + r8 + 16], xmm0
        add     r8, 32
        cmp     rcx, r8
        jne     .LBB0_10
        cmp     rdx, rcx
        je      .LBB0_8
        test    dl, 28
        je      .LBB0_13
.LBB0_5:
        mov     r10, rdx
        and     r10, -4
        lea     r8, [rax + r10]
        mov     r9d, edx
        and     r9d, 3
        movd    xmm0, edi
        punpcklbw       xmm0, xmm0
        pshuflw xmm0, xmm0, 0
.LBB0_6:
        movd    dword ptr [rax + rcx], xmm0
        add     rcx, 4
        cmp     r10, rcx
        jne     .LBB0_6
        cmp     rdx, r10
        je      .LBB0_8
.LBB0_15:
        xor     ecx, ecx
.LBB0_16:
        mov     byte ptr [r8 + rcx], sil
        inc     rcx
        cmp     r9, rcx
        jne     .LBB0_16
.LBB0_8:
        ret
.LBB0_13:
        add     rcx, rax
        and     edx, 31
        mov     r8, rcx
        jmp     .LBB0_14
Poznámka: v DOSu budou výsledky jiné, protože se bude jednat o šestnáctibitový kód.

14. Výsledky vygenerované překladačem Borland C++

Podívejme se nyní na způsob překladu funkce memset překladačem Borland C++ 2.0. V případě, že nejsou povoleny optimalizace, je výsledkem kód, který sice není z dnešního pohledu optimální, ovšem alespoň ve vnitřní smyčce využívá pracovní registry (samozřejmě se zápisem hodnoty prvku do paměti):

@1@50:
        mov     al,dl
        mov     byte ptr [si],al
        inc     si
@1@74:
        mov     ax,di
        dec     di
        or      ax,ax
        ja      short @1@50

Nejedná se o ideální kód, protože se zde registr AX/AL používá ve dvou významech, což je zbytečné, protože by namísto toho bylo možné využít další pracovní registr. I test na ukončení počítané smyčky lze napsat jednodušeji. Celý kód přeložené funkce memset vypadá takto:

_TEXT   segment byte public 'CODE'
   ;
   ;    void * memset(void *dest, register int val, register size_t len) {
   ;
        assume  cs:_TEXT
memset  proc    near
        push    bp
        mov     bp,sp
        push    si
        push    di
        mov     bx,word ptr [bp+4]
        mov     dx,word ptr [bp+6]
        mov     di,word ptr [bp+8]
   ;
   ;        register unsigned char *ptr = (unsigned char*)dest;
   ;
        mov     si,bx
        jmp     short @1@74
@1@50:
   ;
   ;        while (len-- > 0)
   ;            *ptr++ = val;
   ;
        mov     al,dl
        mov     byte ptr [si],al
        inc     si
@1@74:
        mov     ax,di
        dec     di
        or      ax,ax
        ja      short @1@50
   ;
   ;        return dest;
   ;
        mov     ax,bx
        jmp     short @1@122
@1@122:
   ;
   ;    }
   ;
        pop     di
        pop     si
        pop     bp
        ret
memset  endp
        ?debug  C E9
_TEXT   ends
_DATA   segment word public 'DATA'
s@      label   byte
_DATA   ends
_TEXT   segment byte public 'CODE'
_TEXT   ends
        public  memset
        end

Jedinou optimalizaci, kterou dovede překladač provést, je odstranění zbytečného skoku (což ale vlastně není optimalizace kódu, ale optimalizace „smetí“ vložených samotným překladačem):

        jmp     short @1@122
@1@122:

A takto vypadá „optimalizovaná“ varianta přeložené funkce memset:

_TEXT   segment byte public 'CODE'
   ;
   ;    void * memset(void *dest, register int val, register size_t len) {
   ;
        assume  cs:_TEXT
memset  proc    near
        push    bp
        mov     bp,sp
        push    si
        push    di
        mov     bx,word ptr [bp+4]
        mov     dx,word ptr [bp+6]
        mov     di,word ptr [bp+8]
   ;
   ;        register unsigned char *ptr = (unsigned char*)dest;
   ;
        mov     si,bx
        jmp     short @1@74
@1@50:
   ;
   ;        while (len-- > 0)
   ;            *ptr++ = val;
   ;
        mov     al,dl
        mov     byte ptr [si],al
        inc     si
@1@74:
        mov     ax,di
        dec     di
        or      ax,ax
        ja      short @1@50
   ;
   ;        return dest;
   ;
        mov     ax,bx
   ;
   ;    }
   ;
        pop     di
        pop     si
        pop     bp
        ret
memset  endp

15. Výsledky vygenerované překladačem Watcom C

Překladač Watcom dokázal vnitřní smyčku přeložit s testem na začátku. Konkrétně se zde testuje podtečení hodnoty přes nulu (což je samo o sobě zajímavé):

L$1:
    dec         ax
    cmp         ax,0xffff
    je          L$2
    mov         byte ptr [bx],dl
    inc         bx
    jmp         L$1
L$2:

Takto vypadá celý kód vygenerovaný Watcom C a zpětně přeložený přes jeho disassembler při vypnutých optimalizacích:

Module: C:\memset.c
GROUP: 'DGROUP' CONST,CONST2,_DATA
 
Segment: _TEXT BYTE USE16 0000001E bytes
0000                          memset_:
0000    50                        push        ax
0001    B8 04 00                  mov         ax,0x0004
0004    E8 00 00                  call        __STK
0007    58                        pop         ax
0008    51                        push        cx
0009    89 C1                     mov         cx,ax
000B    89 D8                     mov         ax,bx
000D    89 CB                     mov         bx,cx
000F                          L$1:
000F    48                        dec         ax
0010    3D FF FF                  cmp         ax,0xffff
0013    74 05                     je          L$2
0015    88 17                     mov         byte ptr [bx],dl
0017    43                        inc         bx
0018    EB F5                     jmp         L$1
001A                          L$2:
001A    89 C8                     mov         ax,cx
001C    59                        pop         cx
001D    C3                        ret
 
Routine Size: 30 bytes,    Routine Base: _TEXT + 0000
 
No disassembly errors
 
Segment: CONST WORD USE16 00000000 bytes
 
Segment: CONST2 WORD USE16 00000000 bytes
 
Segment: _DATA WORD USE16 00000000 bytes

V případě, že optimalizace povolíme, nedojde (kupodivu) k další optimalizaci vnitřní smyčky, ale pouze se odstraní kontrola přetečení zásobníku na začátku funkce. Tím pádem se nemusí ukládat registr AX na zásobník – to se provede jen s registrem CX. Prolog a epilog funkce je tedy zjednodušen:

Module: c:\memset.c
GROUP: 'DGROUP' CONST,CONST2,_DATA
 
Segment: _TEXT BYTE USE16 00000016 bytes
0000                          memset_:
0000    51                        push        cx
0001    89 C1                     mov         cx,ax
0003    89 D8                     mov         ax,bx
0005    89 CB                     mov         bx,cx
0007                          L$1:
0007    48                        dec         ax
0008    3D FF FF                  cmp         ax,0xffff
000B    74 05                     je          L$2
000D    88 17                     mov         byte ptr [bx],dl
000F    43                        inc         bx
0010    EB F5                     jmp         L$1
0012                          L$2:
0012    89 C8                     mov         ax,cx
0014    59                        pop         cx
0015    C3                        ret
 
Routine Size: 22 bytes,    Routine Base: _TEXT + 0000
 
No disassembly errors
 
Segment: CONST WORD USE16 00000000 bytes
 
Segment: CONST2 WORD USE16 00000000 bytes
 
Segment: _DATA WORD USE16 00000000 bytes

Pro zajímavost se podívejme, k jakým změnám dojde v případě, že použijeme překlad do 32bitového kódu. Kód se kupodivu zjednodušil, protože nyní se pro adresaci používá přímo registru EAX, zatímco v předchozím kódu se musel používat registr BX (možnosti adresování jsou v šestnáctibitovém režimu omezenější):

Module: c:\memset.c
GROUP: 'DGROUP' CONST,CONST2,_DATA
 
Segment: _TEXT BYTE USE32 00000012 bytes
0000                          memset_:
0000    51                        push        ecx
0001    89 C1                     mov         ecx,eax
0003                          L$1:
0003    4B                        dec         ebx
0004    83 FB FF                  cmp         ebx,0xffffffff
0007    74 05                     je          L$2
0009    88 10                     mov         byte ptr [eax],dl
000B    40                        inc         eax
000C    EB F5                     jmp         L$1
000E                          L$2:
000E    89 C8                     mov         eax,ecx
0010    59                        pop         ecx
0011    C3                        ret
 
Routine Size: 18 bytes,    Routine Base: _TEXT + 0000
 
No disassembly errors
 
Segment: CONST DWORD USE32 00000000 bytes
 
Segment: CONST2 DWORD USE32 00000000 bytes
 
Segment: _DATA DWORD USE32 00000000 bytes

16. Příklad třetí: nalezení prvku v poli s maximální hodnotou

Třetí demonstrační příklad naprogramovaný v jazyku C bude nepatrně složitější, než předchozí dvojice příkladů. Je v něm implementována funkce nazvaná find_max, které se předá pole a jeho délka. Funkce nalezne prvek s maximální hodnotou a tu vrátí. Pokud je pole prázdné, vrátí se implicitní hodnota 0, protože (schválně z tohoto důvodu) jsou prvky pole typu unsigned int:

#include <stdio.h>
 
typedef unsigned int uint;
 
uint find_max(uint *array, uint length) {
    uint max = 0;
    uint i;
    uint *item = array;
 
    for (i=0; i < length; i++) {
        if (max < *item) {
            max = *item;
        }
        item++;
    }
    return max;
}
 
int main(void) {
#define LENGTH 10
 
    uint array[LENGTH] = {5, 6, 7, 8, 9, 0, 1, 2, 3, 4};
    uint max = find_max(array, LENGTH);
    printf("%d\n", max);
    return 0;
}
Poznámka: opět překladačům pomáháme, protože k prvkům pole přistupujeme přes ukazatel.

Překlad této funkce překladačem GCC pro moderní platformu x86–64 dopadne takto:

find_max:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-24], rdi
        mov     DWORD PTR [rbp-28], esi
        mov     DWORD PTR [rbp-4], 0
        mov     rax, QWORD PTR [rbp-24]
        mov     QWORD PTR [rbp-16], rax
        mov     DWORD PTR [rbp-8], 0
        jmp     .L2
.L4:
        mov     rax, QWORD PTR [rbp-16]
        mov     eax, DWORD PTR [rax]
        cmp     DWORD PTR [rbp-4], eax
        jnb     .L3
        mov     rax, QWORD PTR [rbp-16]
        mov     eax, DWORD PTR [rax]
        mov     DWORD PTR [rbp-4], eax
.L3:
        add     QWORD PTR [rbp-16], 4
        add     DWORD PTR [rbp-8], 1
.L2:
        mov     eax, DWORD PTR [rbp-8]
        cmp     eax, DWORD PTR [rbp-28]
        jb      .L4
        mov     eax, DWORD PTR [rbp-4]
        pop     rbp
        ret

Jedná se o dosti otrocký a neoptimální kód, protože se přímo pracuje s proměnnými uloženými na zásobníkovém rámci a registru RAX či EAX je používán jen dočasně.

Zajímavý je výsledek překladu se snahou o vytvoření co nejkratšího kódu (-Os). Využívá se zde instrukce CMOVB, kterou jsme si ještě nepopsali, ale její funkce je zřejmá – přenos při splnění podmínky (zde podmínky below):

find_max:
        xor     edx, edx
        xor     eax, eax
.L2:
        cmp     edx, esi
        jnb     .L5
        mov     ecx, DWORD PTR [rdi+rdx*4]
        cmp     eax, ecx
        cmovb   eax, ecx
        inc     rdx
        jmp     .L2
.L5:
        ret

A konečně vektorizovaná varianta, která by měla být pro delší pole nejrychlejší. Takto rozbalený kód by pravděpodobně lidský vývojář nikdy nenapsal:

find_max:
        mov     rcx, rdi
        test    esi, esi
        je      .L7
        lea     eax, [rsi-1]
        cmp     eax, 5
        jbe     .L8
        mov     edx, esi
        pcmpeqd xmm3, xmm3
        pxor    xmm2, xmm2
        mov     rax, rdi
        shr     edx, 2
        pslld   xmm3, 31
        sal     rdx, 4
        add     rdx, rdi
.L4:
        movdqu  xmm1, XMMWORD PTR [rax]
        movdqa  xmm4, xmm2
        add     rax, 16
        psubd   xmm4, xmm3
        movdqa  xmm0, xmm1
        psubd   xmm0, xmm3
        pcmpgtd xmm0, xmm4
        pand    xmm1, xmm0
        pandn   xmm0, xmm2
        movdqa  xmm2, xmm0
        por     xmm2, xmm1
        cmp     rax, rdx
        jne     .L4
        movdqa  xmm1, xmm2
        movdqa  xmm4, xmm2
        psrldq  xmm1, 8
        psubd   xmm4, xmm3
        movdqa  xmm0, xmm1
        psubd   xmm0, xmm3
        pcmpgtd xmm0, xmm4
        pand    xmm1, xmm0
        pandn   xmm0, xmm2
        por     xmm0, xmm1
        movdqa  xmm2, xmm0
        movdqa  xmm4, xmm0
        psrldq  xmm2, 4
        psubd   xmm4, xmm3
        movdqa  xmm1, xmm2
        psubd   xmm1, xmm3
        pcmpgtd xmm1, xmm4
        pand    xmm2, xmm1
        pandn   xmm1, xmm0
        por     xmm1, xmm2
        movd    eax, xmm1
        test    sil, 3
        je      .L1
        mov     edx, esi
        and     edx, -4
        mov     edi, edx
        lea     rcx, [rcx+rdi*4]
.L3:
        mov     edi, DWORD PTR [rcx]
        cmp     eax, edi
        cmovb   eax, edi
        lea     edi, [rdx+1]
        cmp     edi, esi
        jnb     .L1
        mov     edi, DWORD PTR [rcx+4]
        cmp     eax, edi
        cmovb   eax, edi
        lea     edi, [rdx+2]
        cmp     edi, esi
        jnb     .L1
        mov     edi, DWORD PTR [rcx+8]
        cmp     eax, edi
        cmovb   eax, edi
        lea     edi, [rdx+3]
        cmp     edi, esi
        jnb     .L1
        mov     edi, DWORD PTR [rcx+12]
        cmp     eax, edi
        cmovb   eax, edi
        lea     edi, [rdx+4]
        cmp     edi, esi
        jnb     .L1
        mov     edi, DWORD PTR [rcx+16]
        cmp     eax, edi
        cmovb   eax, edi
        add     edx, 5
        cmp     edx, esi
        jnb     .L1
        mov     edx, DWORD PTR [rcx+20]
        cmp     eax, edx
        cmovb   eax, edx
        ret
.L7:
        xor     eax, eax
.L1:
        ret
.L8:
        xor     edx, edx
        xor     eax, eax
        jmp     .L3

17. Výsledky vygenerované překladačem Borland C++

Nyní se vraťme do doby šestnáctibitových mikroprocesorů a operačního systému DOS. Podíváme se, jak byla funkce find_max přeložena překladačem Borland C++. Výsledkem sice není dobře optimalizovaný kód, ale minimálně se do značné míry používají pracovní registry a nikoli pomocné proměnné uložené na zásobníkovém rámci (například maximální hodnota je uložena v DI atd.). Ovšem s jednou výjimkou – testem na konec smyčky, který je realizován takto:

        mov     ax,dx
        cmp     ax,word ptr [bp+6]
        jb      short @1@50

Přičemž je zřejmé, že první instrukce je zbytečná.

Taktéž v kódu nalezneme zbytečný skok:

        jmp     short @1@194
@1@194:

A kvůli tomu, že jsou prvky adresovány přes registr SI, je zvýšení ukazatele realizováno opakovanou instrukcí INC:

        inc     si
        inc     si

Celý výsledný kód vypadá následovně:

find_max        proc    near
        push    bp
        mov     bp,sp
        push    si
        push    di
   ;
   ;        uint max = 0;
   ;
        xor     di,di
   ;
   ;        uint i;
   ;        uint *item = array;
   ;
        mov     si,word ptr [bp+4]
   ;
   ;
   ;        for (i=0; i<length; i++) {
   ;
        xor     dx,dx
        jmp     short @1@146
@1@50:
   ;
   ;            if (max < *item) {
   ;
        cmp     word ptr [si],di
        jbe     short @1@98
   ;
   ;                max = *item;
   ;
        mov     di,word ptr [si]
@1@98:
   ;
   ;            }
   ;            item++;
   ;
        inc     si
        inc     si
        inc     dx
@1@146:
        mov     ax,dx
        cmp     ax,word ptr [bp+6]
        jb      short @1@50
   ;
   ;        }
   ;        return max;
   ;
        mov     ax,di
        jmp     short @1@194
@1@194:
   ;
   ;    }
   ;
        pop     di
        pop     si
        pop     bp
        ret
find_max        endp
Poznámka: povšimněte si, že pokud je nalezen větší prvek, bude se v této iteraci z pole číst dvakrát – což není pro seřazené pole vůbec optimální:
        cmp     word ptr [si],di
        ...
        ...
        ...
        mov     di,word ptr [si]

Po zapnutí optimalizací dokáže překladač změnit jedinou věc – odstranit nepodmíněný skok na následující adresu, který je zcela zbytečný:

find_max        proc    near
        push    bp
        mov     bp,sp
        push    si
        push    di
   ;
   ;        uint max = 0;
   ;
        xor     di,di
   ;
   ;        uint i;
   ;        uint *item = array;
   ;
        mov     si,word ptr [bp+4]
   ;
   ;
   ;        for (i=0; i<length; i++) {
   ;
        xor     dx,dx
        jmp     short @1@146
@1@50:
   ;
   ;            if (max < *item) {
   ;
        cmp     word ptr [si],di
        jbe     short @1@98
   ;
   ;                max = *item;
   ;
        mov     di,word ptr [si]
@1@98:
   ;
   ;            }
   ;            item++;
   ;
        inc     si
        inc     si
        inc     dx
@1@146:
        mov     ax,dx
        cmp     ax,word ptr [bp+6]
        jb      short @1@50
   ;
   ;        }
   ;        return max;
   ;
        mov     ax,di
   ;
   ;    }
   ;
        pop     di
        pop     si
        pop     bp
        ret
find_max        endp

Teoreticky by bylo možné dosáhnout odlišných výsledků povolením instrukcí procesoru Intel 80828. To sice změnilo kód funkce main, ovšem naše funkce find_max se nijak nezměnila:

find_max        proc    near
        push    bp
        mov     bp,sp
        push    si
        push    di
   ;
   ;        uint max = 0;
   ;
        xor     di,di
   ;
   ;        uint i;
   ;        uint *item = array;
   ;
        mov     si,word ptr [bp+4]
   ;
   ;
   ;        for (i=0; i<length; i++) {
   ;
        xor     dx,dx
        jmp     short @1@146
@1@50:
   ;
   ;            if (max < *item) {
   ;
        cmp     word ptr [si],di
        jbe     short @1@98
   ;
   ;                max = *item;
   ;
        mov     di,word ptr [si]
@1@98:
   ;
   ;            }
   ;            item++;
   ;
        inc     si
        inc     si
        inc     dx
@1@146:
        mov     ax,dx
        cmp     ax,word ptr [bp+6]
        jb      short @1@50
   ;
   ;        }
   ;        return max;
   ;
        mov     ax,di
   ;
   ;    }
   ;
        pop     di
        pop     si
        pop     bp
        ret
find_max        endp

18. Výsledky vygenerované překladačem Watcom C/C++

Nyní nám již zbývá prozkoumat, jak byla funkce find_max přeložena pomocí Watcom C/C++. Pro přístup k prvkům pole se nyní používá registr BX a nikoli SI. Počitadlo smyčky je uloženo v registru AX a maximální hodnota v registru DX, takže celá smyčka je realizována následovně:

L$1:
    cmp         ax,cx
    jae         L$3
    cmp         dx,word ptr [bx]
    jae         L$2
    mov         dx,word ptr [bx]
L$2:
    add         bx,0x0002
    inc         ax
    jmp         L$1
L$3:

Kód v assembleru, který není optimalizován, vypadá takto:

Module: C:\find_max.c
GROUP: 'DGROUP' CONST,CONST2,_DATA
 
Segment: _TEXT BYTE USE16 00000027 bytes
0000                          find_max_:
0000    50                        push        ax
0001    B8 06 00                  mov         ax,0x0006
0004    E8 00 00                  call        __STK
0007    58                        pop         ax
0008    53                        push        bx
0009    51                        push        cx
000A    89 D1                     mov         cx,dx
000C    31 D2                     xor         dx,dx
000E    89 C3                     mov         bx,ax
0010    31 C0                     xor         ax,ax
0012                          L$1:
0012    39 C8                     cmp         ax,cx
0014    73 0C                     jae         L$3
0016    3B 17                     cmp         dx,word ptr [bx]
0018    73 02                     jae         L$2
001A    8B 17                     mov         dx,word ptr [bx]
001C                          L$2:
001C    83 C3 02                  add         bx,0x0002
001F    40                        inc         ax
0020    EB F0                     jmp         L$1
0022                          L$3:
0022    89 D0                     mov         ax,dx
0024    59                        pop         cx
0025    5B                        pop         bx
0026    C3                        ret
 
Routine Size: 39 bytes,    Routine Base: _TEXT + 0000
 
No disassembly errors
 
Segment: CONST WORD USE16 00000000 bytes
 
Segment: CONST2 WORD USE16 00000000 bytes
 
Segment: _DATA WORD USE16 00000000 bytes

Po zapnutí optimalizací je odstraněna kontrola přetečení zásobníku a smyčka je přeorganizována do této podoby (s kontrolou ukončení smyčky na konci):

L$1:
    cmp         dx,word ptr [bx]
    jae         L$2
    mov         dx,word ptr [bx]
L$2:
    inc         ax
    add         bx,0x0002
    cmp         ax,cx
    jb          L$1
L$3:

Celý kód je kratší – 33 bajtů oproti původním 39 bajtům:

Module: C:\find_max.c
GROUP: 'DGROUP' CONST,CONST2,_DATA
 
Segment: _TEXT PARA USE16 00000021 bytes
0000                          find_max_:
0000    53                        push        bx
0001    51                        push        cx
0002    89 D1                     mov         cx,dx
0004    89 C3                     mov         bx,ax
0006    31 D2                     xor         dx,dx
0008    31 C0                     xor         ax,ax
000A    85 C9                     test        cx,cx
000C    76 0E                     jbe         L$3
000E                          L$1:
000E    3B 17                     cmp         dx,word ptr [bx]
0010    73 02                     jae         L$2
0012    8B 17                     mov         dx,word ptr [bx]
0014                          L$2:
0014    40                        inc         ax
0015    83 C3 02                  add         bx,0x0002
0018    39 C8                     cmp         ax,cx
001A    72 F2                     jb          L$1
001C                          L$3:
001C    89 D0                     mov         ax,dx
001E    59                        pop         cx
001F    5B                        pop         bx
0020    C3                        ret
 
Routine Size: 33 bytes,    Routine Base: _TEXT + 0000
 
No disassembly errors
 
Segment: CONST WORD USE16 00000000 bytes
 
Segment: CONST2 WORD USE16 00000000 bytes
 
Segment: _DATA WORD USE16 00000000 bytes

Pro zajímavost se podívejme na 32bitovou variantu, která je ještě kratší, a to díky lepším možnostem adresování v 32bitovém režimu. Samotná smyčka nyní vypadá takto (ECX obsahuje maximální hodnotu, EAX je použit při adresování):

L$1:
    mov         esi,dword ptr [eax]
    cmp         ecx,esi
    jae         L$2
    mov         ecx,esi
L$2:
    inc         edx
    add         eax,0x00000004
    cmp         edx,ebx
    jb          L$1
L$3:
Poznámka: zajímavé je, že nyní překladač kód upravil tak, aby se v každé iteraci do pole přistoupilo pouze jedenkrát, kdežto u předchozího kódu to mohlo být 2× při nalezení většího prvku.

Celý výsledný kód má nyní délku 35 bajtů:

Module: C:\find_max.c
GROUP: 'DGROUP' CONST,CONST2,_DATA
 
Segment: _TEXT PARA USE32 00000023 bytes
0000                          find_max_:
0000    53                        push        ebx
0001    51                        push        ecx
0002    56                        push        esi
0003    89 D3                     mov         ebx,edx
0005    31 C9                     xor         ecx,ecx
0007    31 D2                     xor         edx,edx
0009    85 DB                     test        ebx,ebx
000B    76 10                     jbe         L$3
000D                          L$1:
000D    8B 30                     mov         esi,dword ptr [eax]
000F    39 F1                     cmp         ecx,esi
0011    73 02                     jae         L$2
0013    89 F1                     mov         ecx,esi
0015                          L$2:
0015    42                        inc         edx
0016    83 C0 04                  add         eax,0x00000004
0019    39 DA                     cmp         edx,ebx
001B    72 F0                     jb          L$1
001D                          L$3:
001D    89 C8                     mov         eax,ecx
001F    5E                        pop         esi
0020    59                        pop         ecx
0021    5B                        pop         ebx
0022    C3                        ret
 
Routine Size: 35 bytes,    Routine Base: _TEXT + 0000
 
No disassembly errors
 
Segment: CONST DWORD USE32 00000000 bytes
 
Segment: CONST2 DWORD USE32 00000000 bytes
 
Segment: _DATA DWORD USE32 00000000 bytes

19. Repositář s demonstračními příklady

Demonstrační příklady napsané v jazyce C, které jsou primárně určené pro překlad s využitím překladačů Turbo C a (Open)Watcom C), byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/8bit-fame. Jednotlivé demonstrační příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již poměrně rozsáhlý) repositář:

Borland C:

docker + kubernetes školení s dotací tip

# Příklad Stručný popis Adresa
1 add.c funkce pro součet dvou celých čísel naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compilers/bc/add.c
2 add1.asm překlad přes assembler, externí symboly začínají pomlčkou https://github.com/tisnik/8bit-fame/blob/master/compilers/bc/add1.asm
3 add2.asm překlad přes assembler, externí symboly nejsou přejmenovány https://github.com/tisnik/8bit-fame/blob/master/compilers/bc/add2.asm
4 add3.asm výsledek překladu se zákazem optimalizací (externí symboly s pomlčkou) https://github.com/tisnik/8bit-fame/blob/master/compilers/bc/add3.asm
5 add4.asm výsledek překladu se zákazem optimalizací (externí symboly nejsou přejmenovány) https://github.com/tisnik/8bit-fame/blob/master/compilers/bc/add4.asm
6 add5.asm výsledek překladu s povolením optimalizací https://github.com/tisnik/8bit-fame/blob/master/compilers/bc/add5.asm
7 memset.c funkce pro vyplnění paměťového bloku naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compilers/bc/memset.c
8 memset1.asm výsledek překladu se zákazem optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/bc/memset1.asm
9 memset2.asm výsledek překladu s povolením optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/bc/memset2.asm
10 find_max.c funkce pro nalezení největšího prvku v poli naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compiler­s/bc/find_max.c
11 find_max1.asm výsledek překladu se zákazem optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/bc/find_max1.asm
12 find_max2.asm výsledek překladu s povolením optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/bc/find_max2.asm
13 find_max3.asm výsledek překladu s povolením instrukcí procesorů 80286 https://github.com/tisnik/8bit-fame/blob/master/compiler­s/bc/find_max3.asm

(Open)Watcom pro platformu IBM PC:

# Příklad Stručný popis Adresa
1 add.c funkce pro součet dvou celých čísel naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/add.c
2 add1.asm výsledek překladu se zákazem optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/add1.asm
3 add2.asm výsledek překladu s povolením optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/add2.asm
4 memset.c funkce pro vyplnění paměťového bloku naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/memset.c
5 memset1.asm výsledek překladu se zákazem optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/memset1.asm
6 memset2.asm výsledek překladu s povolením optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/memset2.asm
7 memset3.asm výsledek překladu do 32bitového kódu https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/memset3.asm
8 find_max.c funkce pro nalezení největšího prvku v poli naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/find_max.c
9 find_max1.asm výsledek překladu se zákazem optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/find_max1.asm
10 find_max2.asm výsledek překladu s povolením optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/find_max2.asm
11 find_max3.asm výsledek překladu do 32bitového kódu https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/find_max3.asm

GCC pro platformu x86–64:

# Příklad Stručný popis Adresa
1 add.c funkce pro součet dvou celých čísel naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compilers/gcc/add.c
2 add.asm výsledek překladu do assembleru s použitím optimalizací https://github.com/tisnik/8bit-fame/blob/master/compilers/gcc/add.asm
3 memset.c funkce pro vyplnění paměťového bloku naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compiler­s/gcc/memset.c
4 memset1.asm výsledek překladu se zákazem optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/gcc/memset1.asm
5 memset2.asm výsledek překladu s povolením optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/gcc/memset2.asm
6 find_max.c funkce pro nalezení největšího prvku v poli naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compiler­s/gcc/find_max.c
7 find_max_default.asm výsledek překladu se zákazem optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/gcc/find_max_default.asm
8 find_max_smallest.asm výsledek překladu s optimalizací na velikost kódu https://github.com/tisnik/8bit-fame/blob/master/compiler­s/gcc/find_max_smallest.asm
9 find_max_vector.asm výsledek překladu s optimalizací na rychlost https://github.com/tisnik/8bit-fame/blob/master/compiler­s/gcc/find_max_vector.asm

20. Odkazy na Internetu

  1. The Intel 8088 Architecture and Instruction Set
    https://people.ece.ubc.ca/~ed­c/464/lectures/lec4.pdf
  2. x86 Opcode Structure and Instruction Overview
    https://pnx.tf/files/x86_op­code_structure_and_instruc­tion_overview.pdf
  3. x86 instruction listings (Wikipedia)
    https://en.wikipedia.org/wi­ki/X86_instruction_listin­gs
  4. x86 assembly language (Wikipedia)
    https://en.wikipedia.org/wi­ki/X86_assembly_language
  5. Intel Assembler (Cheat sheet)
    http://www.jegerlehner.ch/in­tel/IntelCodeTable.pdf
  6. 25 Microchips That Shook the World
    https://spectrum.ieee.org/tech-history/silicon-revolution/25-microchips-that-shook-the-world
  7. Chip Hall of Fame: MOS Technology 6502 Microprocessor
    https://spectrum.ieee.org/tech-history/silicon-revolution/chip-hall-of-fame-mos-technology-6502-microprocessor
  8. Chip Hall of Fame: Intel 8088 Microprocessor
    https://spectrum.ieee.org/tech-history/silicon-revolution/chip-hall-of-fame-intel-8088-microprocessor
  9. Jak se zrodil procesor?
    https://www.root.cz/clanky/jak-se-zrodil-procesor/
  10. Apple II History Home
    http://apple2history.org/
  11. The 8086/8088 Primer
    https://www.stevemorse.or­g/8086/index.html
  12. flat assembler: Assembly language resources
    https://flatassembler.net/
  13. FASM na Wikipedii
    https://en.wikipedia.org/wiki/FASM
  14. Fresh IDE FASM inside
    https://fresh.flatassembler.net/
  15. MS-DOS Version 4.0 Programmer's Reference
    https://www.pcjs.org/docu­ments/books/mspl13/msdos/dos­ref40/
  16. DOS API (Wikipedia)
    https://en.wikipedia.org/wiki/DOS_API
  17. Bit banging
    https://en.wikipedia.org/wi­ki/Bit_banging
  18. IBM Basic assembly language and successors (Wikipedia)
    https://en.wikipedia.org/wi­ki/IBM_Basic_assembly_lan­guage_and_successors
  19. X86 Assembly/Bootloaders
    https://en.wikibooks.org/wi­ki/X86_Assembly/Bootloaders
  20. Počátky grafiky na PC: grafické karty CGA a Hercules
    https://www.root.cz/clanky/pocatky-grafiky-na-pc-graficke-karty-cga-a-hercules/
  21. Co mají společného Commodore PET/4000, BBC Micro, Amstrad CPC i grafické karty MDA, CGA a Hercules?
    https://www.root.cz/clanky/co-maji-spolecneho-commodore-pet-4000-bbc-micro-amstrad-cpc-i-graficke-karty-mda-cga-a-hercules/
  22. Karta EGA: první použitelná barevná grafika na PC
    https://www.root.cz/clanky/karta-ega-prvni-pouzitelna-barevna-grafika-na-pc/
  23. RGB Classic Games
    https://www.classicdosgames.com/
  24. Turbo Assembler (Wikipedia)
    https://en.wikipedia.org/wi­ki/Turbo_Assembler
  25. Microsoft Macro Assembler
    https://en.wikipedia.org/wi­ki/Microsoft_Macro_Assembler
  26. IBM Personal Computer (Wikipedia)
    https://en.wikipedia.org/wi­ki/IBM_Personal_Computer
  27. Intel 8251
    https://en.wikipedia.org/wi­ki/Intel_8251
  28. Intel 8253
    https://en.wikipedia.org/wi­ki/Intel_8253
  29. Intel 8255
    https://en.wikipedia.org/wi­ki/Intel_8255
  30. Intel 8257
    https://en.wikipedia.org/wi­ki/Intel_8257
  31. Intel 8259
    https://en.wikipedia.org/wi­ki/Intel_8259
  32. Support/peripheral/other chips – 6800 family
    http://www.cpu-world.com/Support/6800.html
  33. Motorola 6845
    http://en.wikipedia.org/wi­ki/Motorola_6845
  34. The 6845 Cathode Ray Tube Controller (CRTC)
    http://www.tinyvga.com/6845
  35. CRTC operation
    http://www.6502.org/users/an­dre/hwinfo/crtc/crtc.html
  36. The 6845 Cathode Ray Tube Controller (CRTC)
    http://www.tinyvga.com/6845
  37. Motorola 6845 and bitwise graphics
    https://retrocomputing.stac­kexchange.com/questions/10996/mo­torola-6845-and-bitwise-graphics
  38. IBM Monochrome Display Adapter
    http://en.wikipedia.org/wi­ki/Monochrome_Display_Adap­ter
  39. Color Graphics Adapter
    http://en.wikipedia.org/wi­ki/Color_Graphics_Adapter
  40. Color Graphics Adapter and the Brown color in IBM 5153 Color Display
    https://www.aceinnova.com/en/e­lectronics/cga-and-the-brown-color-in-ibm-5153-color-display/
  41. The Modern Retrocomputer: An Arduino Driven 6845 CRT Controller
    https://hackaday.com/2017/05/14/the-modern-retrocomputer-an-arduino-driven-6845-crt-controller/
  42. flat assembler: Assembly language resources
    https://flatassembler.net/
  43. FASM na Wikipedii
    https://en.wikipedia.org/wiki/FASM
  44. Fresh IDE FASM inside
    https://fresh.flatassembler.net/
  45. MS-DOS Version 4.0 Programmer's Reference
    https://www.pcjs.org/docu­ments/books/mspl13/msdos/dos­ref40/
  46. DOS API (Wikipedia)
    https://en.wikipedia.org/wiki/DOS_API
  47. IBM Basic assembly language and successors (Wikipedia)
    https://en.wikipedia.org/wi­ki/IBM_Basic_assembly_lan­guage_and_successors
  48. X86 Assembly/Arithmetic
    https://en.wikibooks.org/wi­ki/X86_Assembly/Arithmetic
  49. Art of Assembly – Arithmetic Instructions
    http://oopweb.com/Assembly/Do­cuments/ArtOfAssembly/Volu­me/Chapter6/CH06–2.html
  50. ASM Flags
    http://www.cavestory.org/gu­ides/csasm/guide/asm_flag­s.html
  51. Status Register
    https://en.wikipedia.org/wi­ki/Status_register
  52. Is it worthwhile to learn x86 assembly language today?
    https://www.quora.com/Is-it-worthwhile-to-learn-x86-assembly-language-today?share=1
  53. Why Learn Assembly Language?
    http://www.codeproject.com/Ar­ticles/89460/Why-Learn-Assembly-Language
  54. Is Assembly still relevant?
    http://programmers.stackex­change.com/questions/95836/is-assembly-still-relevant
  55. Why Learning Assembly Language Is Still a Good Idea
    http://www.onlamp.com/pub/a/on­lamp/2004/05/06/writegreat­code.html
  56. Assembly language today
    http://beust.com/weblog/2004/06/23/as­sembly-language-today/
  57. Assembler: Význam assembleru dnes
    http://www.builder.cz/rubri­ky/assembler/vyznam-assembleru-dnes-155960cz
  58. Programming from the Ground Up Book – Summary
    http://savannah.nongnu.or­g/projects/pgubook/
  59. DOSBox
    https://www.dosbox.com/
  60. The C Programming Language
    https://en.wikipedia.org/wi­ki/The_C_Programming_Langu­age
  61. Hercules Graphics Card (HCG)
    https://en.wikipedia.org/wi­ki/Hercules_Graphics_Card
  62. Complete 8086 instruction set
    https://content.ctcd.edu/cou­rses/cosc2325/m22/docs/emu8086in­s.pdf
  63. Complete 8086 instruction set
    https://yassinebridi.github.io/asm-docs/8086_instruction_set.html
  64. 8088 MPH by Hornet + CRTC + DESiRE (final version)
    https://www.youtube.com/wat­ch?v=hNRO7lno_DM
  65. Area 5150 by CRTC & Hornet (Party Version) / IBM PC+CGA Demo, Hardware Capture
    https://www.youtube.com/wat­ch?v=fWDxdoRTZPc
  66. 80×86 Integer Instruction Set Timings (8088 – Pentium)
    http://aturing.umcs.maine­.edu/~meadow/courses/cos335/80×86-Integer-Instruction-Set-Clocks.pdf
  67. Colour Graphics Adapter: Notes
    https://www.seasip.info/Vin­tagePC/cga.html
  68. Restoring A Vintage CGA Card With Homebrew HASL
    https://hackaday.com/2024/06/12/res­toring-a-vintage-cga-card-with-homebrew-hasl/
  69. Demoing An 8088
    https://hackaday.com/2015/04/10/de­moing-an-8088/
  70. Warnings Are Your Friend – A Code Quality Primer
    https://hackaday.com/2018/11/06/war­nings-are-your-friend-a-code-quality-primer/
  71. Defending Against Compiler-Based Backdoors
    https://blog.regehr.org/archives/1241
  72. Reflections on Trusting Trust
    https://www.win.tue.nl/~a­eb/linux/hh/thompson/trus­t.html
  73. Coding Machines (povídka)
    https://www.teamten.com/law­rence/writings/coding-machines/
  74. Stage0
    https://bootstrapping.mira­heze.org/wiki/Stage0
  75. Projekt stage0 na GitHubu
    https://github.com/oriansj/stage0
  76. Bootstraping wiki
    https://bootstrapping.mira­heze.org/wiki/Main_Page
  77. Bootstrapped 6502 Assembler
    https://github.com/robinluc­key/bootstrap-6502
  78. IBM Basic assembly language and successors (Wikipedia)
    https://en.wikipedia.org/wi­ki/IBM_Basic_assembly_lan­guage_and_successors
  79. X86 Assembly/Bootloaders
    https://en.wikibooks.org/wi­ki/X86_Assembly/Bootloaders
  80. What is a coder's worst nightmare?
    https://www.quora.com/What-is-a-coders-worst-nightmare/answer/Mick-Stute
  81. Tiny C Compiler
    https://bellard.org/tcc/
  82. Welcome to C–
    https://www.cs.tufts.edu/~nr/c--/index.html
  83. c4 – C in four functions
    https://github.com/rswier/c4
  84. Tiobe index
    https://www.tiobe.com/tiobe-index/
  85. Lattice C (Wikipedia)
    https://en.wikipedia.org/wi­ki/Lattice_C
  86. Aztec C (Wikipedia)
    https://en.wikipedia.org/wiki/Aztec_C
  87. Digital Mars (Wikipedia)
    https://en.wikipedia.org/wi­ki/Digital_Mars
  88. Stránky projektu Open Watcom
    https://openwatcom.org/
  89. Repositář Open Watcom
    https://github.com/open-watcom/open-watcom-v2
  90. Watcom C/C++ (Wikipedia)
    https://en.wikipedia.org/wi­ki/Watcom_C/C%2B%2B
  91. Turbo C (Wikipedia)
    https://en.wikipedia.org/wiki/Turbo_C
  92. Borland C++ (Wikipedia)
    https://en.wikipedia.org/wi­ki/Borland_C%2B%2B
Neutrální ikona do widgetu na odběr článků ze seriálů

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.


Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.