Obsah
1. Překladače na platformě IBM PC: od assembleru k 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
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.

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.

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.

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.

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ě.

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].

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.

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
; 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

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

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
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; }
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
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; }
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
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:
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:
# | 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/compilers/bc/memset1.asm |
9 | memset2.asm | výsledek překladu s povolením optimalizací | https://github.com/tisnik/8bit-fame/blob/master/compilers/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/compilers/bc/find_max.c |
11 | find_max1.asm | výsledek překladu se zákazem optimalizací | https://github.com/tisnik/8bit-fame/blob/master/compilers/bc/find_max1.asm |
12 | find_max2.asm | výsledek překladu s povolením optimalizací | https://github.com/tisnik/8bit-fame/blob/master/compilers/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/compilers/bc/find_max3.asm |
(Open)Watcom pro platformu IBM PC:
GCC pro platformu x86–64:
20. Odkazy na Internetu
- The Intel 8088 Architecture and Instruction Set
https://people.ece.ubc.ca/~edc/464/lectures/lec4.pdf - x86 Opcode Structure and Instruction Overview
https://pnx.tf/files/x86_opcode_structure_and_instruction_overview.pdf - x86 instruction listings (Wikipedia)
https://en.wikipedia.org/wiki/X86_instruction_listings - x86 assembly language (Wikipedia)
https://en.wikipedia.org/wiki/X86_assembly_language - Intel Assembler (Cheat sheet)
http://www.jegerlehner.ch/intel/IntelCodeTable.pdf - 25 Microchips That Shook the World
https://spectrum.ieee.org/tech-history/silicon-revolution/25-microchips-that-shook-the-world - Chip Hall of Fame: MOS Technology 6502 Microprocessor
https://spectrum.ieee.org/tech-history/silicon-revolution/chip-hall-of-fame-mos-technology-6502-microprocessor - Chip Hall of Fame: Intel 8088 Microprocessor
https://spectrum.ieee.org/tech-history/silicon-revolution/chip-hall-of-fame-intel-8088-microprocessor - Jak se zrodil procesor?
https://www.root.cz/clanky/jak-se-zrodil-procesor/ - Apple II History Home
http://apple2history.org/ - The 8086/8088 Primer
https://www.stevemorse.org/8086/index.html - flat assembler: Assembly language resources
https://flatassembler.net/ - FASM na Wikipedii
https://en.wikipedia.org/wiki/FASM - Fresh IDE FASM inside
https://fresh.flatassembler.net/ - MS-DOS Version 4.0 Programmer's Reference
https://www.pcjs.org/documents/books/mspl13/msdos/dosref40/ - DOS API (Wikipedia)
https://en.wikipedia.org/wiki/DOS_API - Bit banging
https://en.wikipedia.org/wiki/Bit_banging - IBM Basic assembly language and successors (Wikipedia)
https://en.wikipedia.org/wiki/IBM_Basic_assembly_language_and_successors - X86 Assembly/Bootloaders
https://en.wikibooks.org/wiki/X86_Assembly/Bootloaders - Počátky grafiky na PC: grafické karty CGA a Hercules
https://www.root.cz/clanky/pocatky-grafiky-na-pc-graficke-karty-cga-a-hercules/ - 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/ - Karta EGA: první použitelná barevná grafika na PC
https://www.root.cz/clanky/karta-ega-prvni-pouzitelna-barevna-grafika-na-pc/ - RGB Classic Games
https://www.classicdosgames.com/ - Turbo Assembler (Wikipedia)
https://en.wikipedia.org/wiki/Turbo_Assembler - Microsoft Macro Assembler
https://en.wikipedia.org/wiki/Microsoft_Macro_Assembler - IBM Personal Computer (Wikipedia)
https://en.wikipedia.org/wiki/IBM_Personal_Computer - Intel 8251
https://en.wikipedia.org/wiki/Intel_8251 - Intel 8253
https://en.wikipedia.org/wiki/Intel_8253 - Intel 8255
https://en.wikipedia.org/wiki/Intel_8255 - Intel 8257
https://en.wikipedia.org/wiki/Intel_8257 - Intel 8259
https://en.wikipedia.org/wiki/Intel_8259 - Support/peripheral/other chips – 6800 family
http://www.cpu-world.com/Support/6800.html - Motorola 6845
http://en.wikipedia.org/wiki/Motorola_6845 - The 6845 Cathode Ray Tube Controller (CRTC)
http://www.tinyvga.com/6845 - CRTC operation
http://www.6502.org/users/andre/hwinfo/crtc/crtc.html - The 6845 Cathode Ray Tube Controller (CRTC)
http://www.tinyvga.com/6845 - Motorola 6845 and bitwise graphics
https://retrocomputing.stackexchange.com/questions/10996/motorola-6845-and-bitwise-graphics - IBM Monochrome Display Adapter
http://en.wikipedia.org/wiki/Monochrome_Display_Adapter - Color Graphics Adapter
http://en.wikipedia.org/wiki/Color_Graphics_Adapter - Color Graphics Adapter and the Brown color in IBM 5153 Color Display
https://www.aceinnova.com/en/electronics/cga-and-the-brown-color-in-ibm-5153-color-display/ - The Modern Retrocomputer: An Arduino Driven 6845 CRT Controller
https://hackaday.com/2017/05/14/the-modern-retrocomputer-an-arduino-driven-6845-crt-controller/ - flat assembler: Assembly language resources
https://flatassembler.net/ - FASM na Wikipedii
https://en.wikipedia.org/wiki/FASM - Fresh IDE FASM inside
https://fresh.flatassembler.net/ - MS-DOS Version 4.0 Programmer's Reference
https://www.pcjs.org/documents/books/mspl13/msdos/dosref40/ - DOS API (Wikipedia)
https://en.wikipedia.org/wiki/DOS_API - IBM Basic assembly language and successors (Wikipedia)
https://en.wikipedia.org/wiki/IBM_Basic_assembly_language_and_successors - X86 Assembly/Arithmetic
https://en.wikibooks.org/wiki/X86_Assembly/Arithmetic - Art of Assembly – Arithmetic Instructions
http://oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter6/CH06–2.html - ASM Flags
http://www.cavestory.org/guides/csasm/guide/asm_flags.html - Status Register
https://en.wikipedia.org/wiki/Status_register - Is it worthwhile to learn x86 assembly language today?
https://www.quora.com/Is-it-worthwhile-to-learn-x86-assembly-language-today?share=1 - Why Learn Assembly Language?
http://www.codeproject.com/Articles/89460/Why-Learn-Assembly-Language - Is Assembly still relevant?
http://programmers.stackexchange.com/questions/95836/is-assembly-still-relevant - Why Learning Assembly Language Is Still a Good Idea
http://www.onlamp.com/pub/a/onlamp/2004/05/06/writegreatcode.html - Assembly language today
http://beust.com/weblog/2004/06/23/assembly-language-today/ - Assembler: Význam assembleru dnes
http://www.builder.cz/rubriky/assembler/vyznam-assembleru-dnes-155960cz - Programming from the Ground Up Book – Summary
http://savannah.nongnu.org/projects/pgubook/ - DOSBox
https://www.dosbox.com/ - The C Programming Language
https://en.wikipedia.org/wiki/The_C_Programming_Language - Hercules Graphics Card (HCG)
https://en.wikipedia.org/wiki/Hercules_Graphics_Card - Complete 8086 instruction set
https://content.ctcd.edu/courses/cosc2325/m22/docs/emu8086ins.pdf - Complete 8086 instruction set
https://yassinebridi.github.io/asm-docs/8086_instruction_set.html - 8088 MPH by Hornet + CRTC + DESiRE (final version)
https://www.youtube.com/watch?v=hNRO7lno_DM - Area 5150 by CRTC & Hornet (Party Version) / IBM PC+CGA Demo, Hardware Capture
https://www.youtube.com/watch?v=fWDxdoRTZPc - 80×86 Integer Instruction Set Timings (8088 – Pentium)
http://aturing.umcs.maine.edu/~meadow/courses/cos335/80×86-Integer-Instruction-Set-Clocks.pdf - Colour Graphics Adapter: Notes
https://www.seasip.info/VintagePC/cga.html - Restoring A Vintage CGA Card With Homebrew HASL
https://hackaday.com/2024/06/12/restoring-a-vintage-cga-card-with-homebrew-hasl/ - Demoing An 8088
https://hackaday.com/2015/04/10/demoing-an-8088/ - Warnings Are Your Friend – A Code Quality Primer
https://hackaday.com/2018/11/06/warnings-are-your-friend-a-code-quality-primer/ - Defending Against Compiler-Based Backdoors
https://blog.regehr.org/archives/1241 - Reflections on Trusting Trust
https://www.win.tue.nl/~aeb/linux/hh/thompson/trust.html - Coding Machines (povídka)
https://www.teamten.com/lawrence/writings/coding-machines/ - Stage0
https://bootstrapping.miraheze.org/wiki/Stage0 - Projekt stage0 na GitHubu
https://github.com/oriansj/stage0 - Bootstraping wiki
https://bootstrapping.miraheze.org/wiki/Main_Page - Bootstrapped 6502 Assembler
https://github.com/robinluckey/bootstrap-6502 - IBM Basic assembly language and successors (Wikipedia)
https://en.wikipedia.org/wiki/IBM_Basic_assembly_language_and_successors - X86 Assembly/Bootloaders
https://en.wikibooks.org/wiki/X86_Assembly/Bootloaders - What is a coder's worst nightmare?
https://www.quora.com/What-is-a-coders-worst-nightmare/answer/Mick-Stute - Tiny C Compiler
https://bellard.org/tcc/ - Welcome to C–
https://www.cs.tufts.edu/~nr/c--/index.html - c4 – C in four functions
https://github.com/rswier/c4 - Tiobe index
https://www.tiobe.com/tiobe-index/ - Lattice C (Wikipedia)
https://en.wikipedia.org/wiki/Lattice_C - Aztec C (Wikipedia)
https://en.wikipedia.org/wiki/Aztec_C - Digital Mars (Wikipedia)
https://en.wikipedia.org/wiki/Digital_Mars - Stránky projektu Open Watcom
https://openwatcom.org/ - Repositář Open Watcom
https://github.com/open-watcom/open-watcom-v2 - Watcom C/C++ (Wikipedia)
https://en.wikipedia.org/wiki/Watcom_C/C%2B%2B - Turbo C (Wikipedia)
https://en.wikipedia.org/wiki/Turbo_C - Borland C++ (Wikipedia)
https://en.wikipedia.org/wiki/Borland_C%2B%2B