V předchozím článku věnovaném použití assembleru v Linuxu jsme se seznámili se základními vlastnosti matematického koprocesoru využívaného na platformách i386 a x86-64. Dnes si vyzkoušíme aplikaci některých vybraných instrukcí v trojici demonstračních příkladů. Ukážeme si i některé mezní případy a výjimky, které mohou při výpočtech nastat – dělení nulou atd.

Obsah

1. Použití assembleru v Linuxu: práce s matematickým koprocesorem (pokračování)

2. Načtení FP konstanty do registru mikroprocesoru s následným uložením konstanty do paměti

3. První demonstrační příklad: vytištění hodnot 0.0, 1.0 a Pi v hexadecimálním tvaru

4. Jak přečíst a dekódovat vytištěné výsledky?

5. Pomocný program pro převod FPU hodnot do jejich hexadecimální podoby

6. Základní aritmetické operace v praxi

7. Složitější výrazy a práce se zásobníkem operandů

8. Druhý demonstrační příklad: základní aritmetické operace

9. Dělení kladnou a zápornou nulou

10. Dělení nuly nulou aneb práce s NaN

11. Třetí demonstrační příklad: dělení nulou

12. Pomocné zdrojové soubory

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

14. Odkazy na Internetu

1. Použití assembleru v Linuxu: práce s matematickým koprocesorem (pokračování)

V předchozí části seriálu o použití assembleru v Linuxu jsme se seznámili se základními koncepty, na nichž je postaven matematický koprocesor používaný na architekturách i386 a x86-64. Připomeňme si, že matematický koprocesor obsahuje osm pracovních registrů, každý o šířce osmdesáti bitů. Tyto registry jsou doplněny o řídicí registr a stavový registr. Dnes si ve třech demonstračních příkladech ukážeme, jakým způsobem je možné matematický koprocesor použít pro základní výpočty, zejména pro aritmetické operace. Taktéž si ukážeme, co se stane při dělení nenulového čísla nulou (což je zcela legální operace) i při pokusu o dělení nuly nulou. Demonstrační příklady jsou primárně odladěny pro 32bitovou platformu i386 (běží ovšem samozřejmě i v 64bitovém systému), ovšem po nepatrné úpravě je lze použít i v čistém 64bitovém režimu.

2. Načtení FP konstanty do registru mikroprocesoru s následným uložením konstanty do paměti

Matematický koprocesor obsahuje několik instrukcí, které je možné použít pro uložení konstanty do pracovního registru procesoru. Mezi základní konstanty patří kladná nula +0,0, kladná jednička +1,0, konstanta π a taktéž na první pohled možná poněkud zbytečné, ale v praxi používané konstanty log210, log2e, log102 a loge2 (u některých výpočtů totiž může být výhodné nejprve operandy zlogaritmovat a posléze je namísto násobení pouze sečíst atd.). Všechny instrukce, které do pracovního registru matematického koprocesoru, konkrétně do registru, jenž aktuálně leží na vrcholu zásobníku, uloží příslušnou konstantu, jsou vypsány v následující tabulce:

# Instrukce Význam
1 FLDZ načtení konstanty +0,0 (kladná nula)
2 FLD1 načtení konstanty +1,0
3 FLDPI načtení konstanty π
4 FLDL2T načtení konstanty log210
5 FLDL2E načtení konstanty log2e
6 FLDLG2 načtení konstanty log102
7 FLDLN2 načtení konstanty loge2

Jakým způsobem se však přesvědčíme o tom, že skutečně došlo k uložení zvolené konstanty do vybraného pracovního registru matematického koprocesoru? Kupodivu není k dispozici žádná instrukce, která by data přímo převedla z matematického koprocesoru do hlavního procesoru, ovšem můžeme využít toho, že obsah libovolného pracovního registru matematického koprocesoru je možné převést do podporovaného formátu (single, double, extended) a uložit zkonvertovanou hodnotu na zvolenou adresu operační paměti. Z této adresy se pak může hodnota načíst do pracovního registru hlavního procesoru a následně se tato hodnota může vypsat například v hexadecimálním tvaru, což již umíme, neboť jsme se touto problematikou již zabývali v předchozích článcích.

Nejdříve je nutné pojmenovat adresu, na kterou se budou hodnoty ukládat. Pro jednoduchost se bude jednat o čtyři bajty alokované v sekci BSS (tato sekce není součástí přeloženého binárního souboru). Čtyři bajty jsou zvoleny proto, že se původně 80bitová hodnota převede do formátu single (číslo s plovoucí řádovou čárkou s jednoduchou přesností). Pro pojmenování (label) zvolíme jméno number:

.section .bss

.lcomm number, 4                     # na toto misto se bude ukladat konstanta typu float

Samotná sekvence instrukcí určených pro vložení konstanty do pracovního registru matematického koprocesoru, následné uložení této hodnoty do operační paměti, načtení uložené hodnoty do pracovního registru hlavního procesoru a vypsání hodnoty v hexadecimální tvaru vypadá následovně:

fldz                         # nacteni FP konstanty 0.0
fstp dword ptr number        # ulozeni do pameti (4 bajty)
mov  eax, dword ptr number   # nacteni hodnoty, tentokrat to celociselneho registru
printHexNumber eax           # vytiskneme celociselnou hodnotu v hexa tvaru

Povšimněte si, že při ukládání do operační paměti musíme přes dword ptr přesně určit způsob konverze (zde na čtyři bajty = single). Totéž platí při zpětném načítání hodnoty do 32bitového pracovního registru EAX. Následně voláme makro printHexNumber, jehož zdrojový kód naleznete ve dvanácté kapitole.

Poznámka: instrukce FSTP provádí kombinaci operací FST+POP, tedy konverzi a uložení obsahu pracovního registru následovanou odstraněním hodnoty ze zásobníku (což se provádí pouhým posunem indexu vrcholu zásobníku o jedničku).

3. První demonstrační příklad: vytištění hodnot 0.0, 1.0 a Pi v hexadecimálním tvaru

Dnešní první demonstrační příklad je založen na sekvenci tří instrukcí FLDX, FSTP adresa a MOV eax, adresa následovaných voláním makra printHexNumber. Tímto způsobem jsou postupně vypsány hodnoty +0,0, +1,0 a π, a to v hexadecimálním tvaru. Nejprve se podívejme na zdrojový kód, který tvoří hlavní část programu, zbylé tři moduly jsou opět popsány ve dvanácté kapitole:

# asmsyntax=as

# Program pro otestovani zakladnich FP operaci
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky

.intel_syntax noprefix



# Nacteni definice makra pro ukonceni aplikace
.include "exit.s"

# Nacteni maker pro (opakovany) tisk zpravy i prislusne subrutiny
.include "writeMessage.s"

# Nacteni makra pro vytisteni hexadecimalni 32bitove hodnoty
# spolecne s makrem je nactena i prislusna subrutina
.include "printHexNumber.s"



#-----------------------------------------------------------------------------
.section .data
fpuValueZeroMessage:
        .string "0.0: "                             # zprava
fpuValueZeroMessageLength = $ - fpuValueZeroMessage # delka zpravy

fpuValueOneMessage:
        .string "1.0: "                             # zprava
fpuValueOneMessageLength = $ - fpuValueOneMessage   # delka zpravy

fpuValuePiMessage:
        .string "Pi:  "                             # zprava
fpuValuePiMessageLength = $ - fpuValuePiMessage     # delka zpravy



#-----------------------------------------------------------------------------
.section .bss

.lcomm number, 4                     # na toto misto se bude ukladat konstanta typu float



#-----------------------------------------------------------------------------
.section .text
        .global _start               # tento symbol ma byt dostupny i linkeru

_start:
        writeMessage fpuValueZeroMessage, fpuValueZeroMessageLength

        fldz                         # nacteni FP konstanty 0.0
        fstp dword ptr number        # ulozeni do pameti (4 bajty)
        mov  eax, dword ptr number   # nacteni hodnoty, tentokrat to celociselneho registru
        printHexNumber eax           # vytiskneme celociselnou hodnotu v hexa tvaru



        writeMessage fpuValueOneMessage, fpuValueOneMessageLength

        fld1                         # nacteni FP konstanty 1.0
        fstp dword ptr number        # ulozeni do pameti (4 bajty)
        mov  eax, dword ptr number   # nacteni hodnoty, tentokrat to celociselneho registru
        printHexNumber eax           # vytiskneme celociselnou hodnotu v hexa tvaru



        writeMessage fpuValuePiMessage, fpuValuePiMessageLength

        fldpi                        # nacteni FP konstanty Pi
        fstp dword ptr number        # ulozeni do pameti (4 bajty)
        mov  eax, dword ptr number   # nacteni hodnoty, tentokrat to celociselneho registru
        printHexNumber eax           # vytiskneme celociselnou hodnotu v hexa tvaru



        println                      # odradkovani

        exit                         # ukonceni aplikace

Překlad a slinkování se provede následujícím způsobem:

as -g --32 main.s -o main.o
ld -m elf_i386 -s main.o

Výsledkem je binární soubor spustitelný jak na 32bitovém, tak i na 64bitovém systému.

Pro překlad upraveného kódu určeného pouze pro 64bitový systém použijte příkazy:

as -g  main_x86_64.s -o main.o
ld -s main.o

Pro zajímavost se podívejme na to, jakým způsobem se FPU instrukce přeložily do nativního kódu:

32bitový systém i386:

00000026 <_start>:
  26:   b9 1a 00 00 00          mov    ecx,0x1a
  2b:   ba 06 00 00 00          mov    edx,0x6
  30:   e8 cb ff ff ff          call   0 <write_message>
<strong>  35:   d9 ee                   fldz   </strong>
<strong>  37:   d9 1d 00 00 00 00       fstp   DWORD PTR ds:0x0</strong>
  3d:   a1 00 00 00 00          mov    eax,ds:0x0
  42:   60                      pusha  
  43:   89 c2                   mov    edx,eax
  45:   bb 10 00 00 00          mov    ebx,0x10
  4a:   e8 be ff ff ff          call   d <hex2string>
  4f:   b9 02 00 00 00          mov    ecx,0x2
  54:   ba 18 00 00 00          mov    edx,0x18
  59:   e8 a2 ff ff ff          call   0 <write_message>
  5e:   61                      popa   
  5f:   b9 20 00 00 00          mov    ecx,0x20
  64:   ba 06 00 00 00          mov    edx,0x6
  69:   e8 92 ff ff ff          call   0 <write_message>
<strong>  6e:   d9 e8                   fld1   </strong>
<strong>  70:   d9 1d 00 00 00 00       fstp   DWORD PTR ds:0x0</strong>
  76:   a1 00 00 00 00          mov    eax,ds:0x0
  7b:   60                      pusha  
  7c:   89 c2                   mov    edx,eax
  7e:   bb 10 00 00 00          mov    ebx,0x10
  83:   e8 85 ff ff ff          call   d <hex2string>
  88:   b9 02 00 00 00          mov    ecx,0x2
  8d:   ba 18 00 00 00          mov    edx,0x18
  92:   e8 69 ff ff ff          call   0 <write_message>
  97:   61                      popa   
  98:   b9 26 00 00 00          mov    ecx,0x26
  9d:   ba 06 00 00 00          mov    edx,0x6
  a2:   e8 59 ff ff ff          call   0 <write_message>
<strong>  a7:   d9 eb                   fldpi  </strong>
<strong>  a9:   d9 1d 00 00 00 00       fstp   DWORD PTR ds:0x0</strong>
  af:   a1 00 00 00 00          mov    eax,ds:0x0
  b4:   60                      pusha  
  b5:   89 c2                   mov    edx,eax
  b7:   bb 10 00 00 00          mov    ebx,0x10
  bc:   e8 4c ff ff ff          call   d <hex2string>
  c1:   b9 02 00 00 00          mov    ecx,0x2
  c6:   ba 18 00 00 00          mov    edx,0x18
  cb:   e8 30 ff ff ff          call   0 <write_message>
  d0:   61                      popa   
  d1:   b9 00 00 00 00          mov    ecx,0x0
  d6:   ba 02 00 00 00          mov    edx,0x2
  db:   e8 20 ff ff ff          call   0 <write_message>
  e0:   b8 01 00 00 00          mov    eax,0x1
  e5:   bb 00 00 00 00          mov    ebx,0x0
  ea:   cd 80                   int    0x80

64bitový systém x86-64:

0000000000000028 <_start>:
  28:	b9 00 00 00 00       	mov    ecx,0x0
  2d:	ba 06 00 00 00       	mov    edx,0x6
  32:	e8 c9 ff ff ff       	call   0 <write_message>
<strong>  37:	d9 ee                	fldz   </strong>
<strong>  39:	d9 1c 25 00 00 00 00 	fstp   DWORD PTR ds:0x0</strong>
  40:	8b 04 25 00 00 00 00 	mov    eax,DWORD PTR ds:0x0
  47:	50                   	push   rax
  48:	53                   	push   rbx
  49:	51                   	push   rcx
  4a:	52                   	push   rdx
  4b:	89 c2                	mov    edx,eax
  4d:	bb 00 00 00 00       	mov    ebx,0x0
  52:	e8 b6 ff ff ff       	call   d <hex2string>
  57:	b9 00 00 00 00       	mov    ecx,0x0
  5c:	ba 18 00 00 00       	mov    edx,0x18
  61:	e8 9a ff ff ff       	call   0 <write_message>
  66:	5a                   	pop    rdx
  67:	59                   	pop    rcx
  68:	5b                   	pop    rbx
  69:	58                   	pop    rax
  6a:	b9 00 00 00 00       	mov    ecx,0x0
  6f:	ba 06 00 00 00       	mov    edx,0x6
  74:	e8 87 ff ff ff       	call   0 <write_message>
<strong>  79:	d9 e8                	fld1   </strong>
<strong>  7b:	d9 1c 25 00 00 00 00 	fstp   DWORD PTR ds:0x0</strong>
  82:	8b 04 25 00 00 00 00 	mov    eax,DWORD PTR ds:0x0
  89:	50                   	push   rax
  8a:	53                   	push   rbx
  8b:	51                   	push   rcx
  8c:	52                   	push   rdx
  8d:	89 c2                	mov    edx,eax
  8f:	bb 00 00 00 00       	mov    ebx,0x0
  94:	e8 74 ff ff ff       	call   d <hex2string>
  99:	b9 00 00 00 00       	mov    ecx,0x0
  9e:	ba 18 00 00 00       	mov    edx,0x18
  a3:	e8 58 ff ff ff       	call   0 <write_message>
  a8:	5a                   	pop    rdx
  a9:	59                   	pop    rcx
  aa:	5b                   	pop    rbx
  ab:	58                   	pop    rax
  ac:	b9 00 00 00 00       	mov    ecx,0x0
  b1:	ba 06 00 00 00       	mov    edx,0x6
  b6:	e8 45 ff ff ff       	call   0 <write_message>
<strong>  bb:	d9 eb                	fldpi  </strong>
<strong>  bd:	d9 1c 25 00 00 00 00 	fstp   DWORD PTR ds:0x0</strong>
  c4:	8b 04 25 00 00 00 00 	mov    eax,DWORD PTR ds:0x0
  cb:	50                   	push   rax
  cc:	53                   	push   rbx
  cd:	51                   	push   rcx
  ce:	52                   	push   rdx
  cf:	89 c2                	mov    edx,eax
  d1:	bb 00 00 00 00       	mov    ebx,0x0
  d6:	e8 32 ff ff ff       	call   d <hex2string>
  db:	b9 00 00 00 00       	mov    ecx,0x0
  e0:	ba 18 00 00 00       	mov    edx,0x18
  e5:	e8 16 ff ff ff       	call   0 <write_message>
  ea:	5a                   	pop    rdx
  eb:	59                   	pop    rcx
  ec:	5b                   	pop    rbx
  ed:	58                   	pop    rax
  ee:	b9 00 00 00 00       	mov    ecx,0x0
  f3:	ba 02 00 00 00       	mov    edx,0x2
  f8:	e8 03 ff ff ff       	call   0 <write_message>
  fd:	b8 01 00 00 00       	mov    eax,0x1
 102:	bb 00 00 00 00       	mov    ebx,0x0
 107:	cd 80                	int    0x80

V obou případech si povšimněte, že všechny FPU operace začínají prefixovým bajtem s hodnotou D9, za nímž u jednodušších instrukcí následuje jediný bajt s operačním kódem a u instrukcí FLD a FST navíc ještě adresa ze které se mají přečíst data popř. na kterou se mají data naopak uložit.

4. Jak přečíst a dekódovat vytištěné výsledky?

Pokud přeložený program spustíme, měly by se na standardní výstup vypsat následující tři řádky:

0.0: Hex value: 0x00000000
1.0: Hex value: 0x3F800000
Pi:  Hex value: 0x40490FDB

Vidíme, že jsme pro každou hodnotu reprezentovanou v systému plovoucí řádové čárky dostali hexadeciální 32bitové číslo, které reprezentuje binární „otisk“ 32bitové hodnoty typu single. S formátem single jsme se seznámili v předchozím článku, takže jen stručně:

bit 31 30   29 ... 24   23 22   21 ... 3   2   1   0
význam s exponent (8 bitů) mantisa (23 bitů)

Exponent je posunutý o bias nastavený na hodnotu 127. Mantisa u normalizovaných čísel obsahuje jen čísla za (binární) řádovou čárkou, tudíž je k hodnotě mantisy nutné přičíst jedničku. Získané hodnoty tedy můžeme dekódovat:

Hexa Binárně s Exponent Mantisa
0x00000000 00000000000000000000000000000000 + 0 (spec) 0
0x3F800000 00111111100000000000000000000000 + 127-127=0 1,0 + 0,0
0x40490FDB 01000000010010010000111111011011 + 128-127=1 1,0 + 0,57079637050628662109375

První hodnota je zcela jednoznačně kladná nula, druhá hodnota je rovna 1,0×20=1, třetí hodnota je pak rovna 1,57079637050628662109375×21=π (zde konkrétně přibližná hodnota 3.14159274101257324218750). Vidíme, že jak způsob uložení hodnot, tak i jejich zpětné ruční dekódování pracuje spolehlivě.

5. Pomocný program pro převod FPU hodnot do jejich hexadecimální podoby

Hodnoty samozřejmě není nutné převádět ručně. Pro zpětný převod FP hodnot, tedy hodnot reprezentovaných v systému plovoucí řádové čárky do hexadecimální 32bitové reprezentace je možné použít i následující jednoduchý program napsaný v céčku. Pokud při spuštění programu specifikujete na příkazové řádce FP hodnotu, například 0.5, vypíše se ihned její obraz v paměti, tj. to, jak je číslo interně reprezentováno (logiku programu lze v případě potřeby jednoduše i obrátit tak, aby převádět hexadecimální reprezentaci na FP hodnotu):

#include <stdio.h>
#include <stdlib.h>

union {
    float flt;
    int   hex;
} float_hex;

int main(int argc, char **argv)
{
    if (argc == 2) {
        float_hex.flt = atof(argv[1]);
        printf("%08x\n", float_hex.hex);
    }
    return 0;
}

Funkci programu si můžeme jednoduše odzkoušet:

<strong>gcc -o fp2hex fp2hex.c </strong>
 
<strong>./fp2hex 0</strong>
00000000
 
<strong>./fp2hex 1</strong>
3f800000
 
<strong>./fp2hex -1</strong>
bf800000
 
<strong>./fp2hex 3.14</strong>
40490fdb

6. Základní aritmetické operace v praxi

Minule jsme se taktéž seznámili s instrukcemi pro základní aritmetické operace. Jedná se o tyto instrukce:

# Instrukce Význam
1 FADD součet
2 FSUB rozdíl
3 FSUBR rozdíl, ale operandy jsou prohozeny
4 FMUL součin
5 FDIV podíl
6 FDIVR podíl, ale operandy jsou prohozeny

U všech těchto instrukcí lze navíc specifikovat příponu P, podobně jako u FST/FSTP. Pokud je tato přípona uvedena, budou ze zásobníku tvořeném pracovními registry odstraněny oba vstupní operandy, a teprve až poté se na zásobník uloží výsledek aritmetické operace. To mj. znamená, že součet dvou hodnot (zde konkrétně součet dvou jedniček) je možné implementovat následujícím způsobem:

fld1                         # nacteni FP konstanty 1.0
fld1                         # nacteni FP konstanty 1.0
faddp                        # soucet obou hodnot (1.0+1.0)
fstp dword ptr number        # ulozeni do pameti (4 bajty)
 
mov  eax, dword ptr number   # nacteni hodnoty, tentokrat to celociselneho registru
printHexNumber eax           # vytiskneme celociselnou hodnotu v hexa tvaru

Ukázka použití základních aritmetických operací tvoří základ pro dnešní druhý demonstrační příklad, jehož zdrojový kód bude uveden v osmé kapitole.

7. Složitější výrazy a práce se zásobníkem operandů

Způsob načtení konstant do pracovních registrů (a tím pádem i do zásobníku) matematického koprocesoru již známe, takže si nyní již můžeme ukázat, jak se provádí základní aritmetické operace. Začneme podobně jako žáčci v první třídě – součtem dvou jedniček. To se v assembleru provede velmi jednoduše: nejdříve se na vrchol zásobníku, tj. do pracovního registru st(0), uloží první konstanta 1.0, a ve druhém kroku se na posunutý vrchol zásobníku (tj. do sousedního pracovního registru) uloží druhá konstanta 1.0. V kroku třetím se provede instrukce FADDP, která nejenže obě hodnoty uložené na vrcholu zásobníku a těsně pod ním sečte, ale navíc je ještě ze zásobníku odstraní (odstranění druhého operandu je zajištěno použitím FADDP namísto FADD). Celý výpočet tedy může vypadat takto:

fld1                         # nacteni FP konstanty 1.0
fld1                         # nacteni FP konstanty 1.0
faddp                        # soucet obou hodnot (1.0+1.0)

Podívejme se nyní na součet tří hodnot. Zde můžeme postupovat několika způsoby, z nichž nejjednodušší způsob spočívá v uložení všech tří hodnot na zásobník s následným použitím dvojice instrukcí FADDP. První instrukce sečte poslední dvě hodnoty uložené na zásobníku, takže jeho nový obsah bude [1.0, 2.0], druhá instrukce pak sečte 1.0+2.0 s uložením celkového výsledku zpět:

fld1                         # nacteni FP konstanty 1.0
fld1                         # nacteni FP konstanty 1.0
fld1                         # nacteni FP konstanty 1.0
faddp
faddp                        # soucet vsech tri hodnot (1.0+(1.0+1.0))

Pokud budeme chtít vynásobit hodnoty 2.0 a 3.0, můžeme oba výpočty spojit dohromady a díky tomu, že zásobník má kapacitu pro osm hodnot, vyhneme se jakýmkoli přesunům dat mezi pracovními registry

fld1                         # nacteni FP konstanty 1.0
fld1                         # nacteni FP konstanty 1.0
faddp                        # soucet obou hodnot (1.0+1.0)
                             # nyni je na zasobniku ulozena hodnota 2

fld1                         # nacteni FP konstanty 1.0
fld1                         # nacteni FP konstanty 1.0
fld1                         # nacteni FP konstanty 1.0
faddp
faddp                        # soucet vsech tri hodnot (1.0+(1.0+1.0))
                             # vysledek 3.0 je ulozen zpet na zasobnik

fmulp                        # nyni jsou na zasobniku ulozeny hodnoty 2 a 3 ktere vynasobime

fstp dword ptr number        # ulozeni do pameti (4 bajty)

8. Druhý demonstrační příklad: základní aritmetické operace

Ve druhém demonstračním příkladu je ukázán způsob práce se zásobníkem tvořeným osmi pracovními registry matematického koprocesoru. Nejprve je vytištěna hodnota 1.0 způsobem, který již známe z předchozího příkladu. Následně je proveden součet dvou hodnot 1.0 s vytištěním výsledku této operace. Poslední část příkladu provádí výpočet popsaný v předchozí kapitole, tj. 2.0*3.0, ve skutečnosti se však počítá (1.0+1.0)*(1.0+(1.0+1.0)). Po překladu a spuštění by se měly na standardním výstupu objevit následující řádky:

1.0:      Hex value: 0x3F800000
1.0+1.0:  Hex value: 0x40000000
2.0*3.0:  Hex value: 0x40C00000

Zdrojový kód druhého demonstračního příkladu vypadá takto:

# asmsyntax=as

# Program pro otestovani zakladnich FPU operaci
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky

.intel_syntax noprefix



# Nacteni definice makra pro ukonceni aplikace
.include "exit.s"

# Nacteni maker pro (opakovany) tisk zpravy i prislusne subrutiny
.include "writeMessage.s"

# Nacteni makra pro vytisteni hexadecimalni 32bitove hodnoty
# spolecne s makrem je nactena i prislusna subrutina
.include "printHexNumber.s"



#-----------------------------------------------------------------------------
.section .data
fpuValueOneMessage:
        .string "1.0:      "                        # zprava
fpuValueOneMessageLength = $ - fpuValueOneMessage   # delka zpravy

fpuAddResultMessage:
        .string "1.0+1.0:  "                        # zprava
fpuAddResultMessageLength = $ - fpuAddResultMessage # delka zpravy

fpuMulResultMessage:
        .string "2.0*3.0:  "                        # zprava
fpuMulResultMessageLength = $ - fpuMulResultMessage # delka zpravy



#-----------------------------------------------------------------------------
.section .bss

.lcomm number, 4                     # na toto misto se bude ukladat konstanta typu float



#-----------------------------------------------------------------------------
.section .text
        .global _start               # tento symbol ma byt dostupny i linkeru

_start:
        writeMessage fpuValueOneMessage, fpuValueOneMessageLength

        fld1                         # nacteni FP konstanty 1.0
        fstp dword ptr number        # ulozeni do pameti (4 bajty)
        mov  eax, dword ptr number   # nacteni hodnoty, tentokrat to celociselneho registru
        printHexNumber eax           # vytiskneme celociselnou hodnotu v hexa tvaru



        writeMessage fpuAddResultMessage, fpuAddResultMessageLength

        fld1                         # nacteni FP konstanty 1.0
        fld1                         # nacteni FP konstanty 1.0
        faddp                        # soucet obou hodnot (1.0+1.0)
        fstp dword ptr number        # ulozeni do pameti (4 bajty)
        mov  eax, dword ptr number   # nacteni hodnoty, tentokrat to celociselneho registru
        printHexNumber eax           # vytiskneme celociselnou hodnotu v hexa tvaru



        writeMessage fpuMulResultMessage, fpuMulResultMessageLength

        fld1                         # nacteni FP konstanty 1.0
        fld1                         # nacteni FP konstanty 1.0
        faddp                        # soucet obou hodnot (1.0+1.0)

        fld1                         # nacteni FP konstanty 1.0
        fld1                         # nacteni FP konstanty 1.0
        fld1                         # nacteni FP konstanty 1.0
        faddp
        faddp                        # soucet vsech tri hodnot (1.0+(1.0+1.0))

        fmulp                        # nyni jsou na zasobniku ulozeny hodnoty 2 a 3 ktere vynasobime

        fstp dword ptr number        # ulozeni do pameti (4 bajty)
        mov  eax, dword ptr number   # nacteni hodnoty, tentokrat to celociselneho registru
        printHexNumber eax           # vytiskneme celociselnou hodnotu v hexa tvaru



        println                      # odradkovani

        exit                         # ukonceni aplikace

9. Dělení kladnou a zápornou nulou

V předchozím demonstračním příkladu jsme si mj. ukázali instrukci FMULP sloužící k vynásobení obou vstupních operandů. Dá se tedy předpokládat, že bude existovat i opačná instrukce určená pro dělení operandů. Taková instrukce skutečně existuje a její varianty se jmenují FDIV a FDIVP. Samotné dělení je prováděno obvyklým způsobem, ovšem zaměřme se nyní na to, co se stane, pokud se provádí dělení nulou. Ve formátech single i double je znaménko reprezentováno samostatným bitem, což mj. znamená, že existuje kladná a záporná nula.

Dělení kladnou nulou je jednoduché a lze ho realizovat například takto:

fld1                         # nacteni FP konstanty 1.0
fldz                         # nacteni FP konstanty 0.0
fdivp                        # deleni nulou

Dělení zápornou nulou vyžaduje použití instrukce FCHS, která otočí znaménko svého operandu (zde konkrétně oné nuly):

fld1                         # nacteni FP konstanty 1.0
fldz                         # nacteni FP konstanty 0.0
fchs                         # zmena znamenka nuly
fdivp                        # deleni zapornou nulou

Jak budou vypadat výsledky dělení?

1/0:   Hex value: 0x7F800000
-1/0:  Hex value: 0xFF800000

Po převodu na binární tvar a rozdělení bitových polí:

Hexa Binárně s Exponent Mantisa
0x7F800000 01111111100000000000000000000000 + 255 (spec) 0
0xFF800000 11111111100000000000000000000000 - 255 (spec) 0

Oba výsledky se od sebe liší jen nejvyšším bitem, což je znaménko. Dále následuje osm bitů exponentu. Tyto bity jsou v obou případech nastaveny na samé jedničky, samotný exponent je pak nulový. To odpovídá tabulce speciálních hodnot, s níž jsme se seznámili minule:

s-bit exponent mantisa význam šestnáctkově
0 255 0 kladné nekonečno 0x7F800000
1 255 0 záporné nekonečno 0xFF800000

10. Dělení nuly nulou aneb práce s NaN

Ještě jsme si však nevyzkoušeli další speciální případ – dělení 0/0. I to lze realizovat velmi snadno následujícími třemi instrukcemi:

fldz                         # nacteni FP konstanty 0.0
fldz                         # nacteni FP konstanty 0.0
fdivp                        # vypocet 0.0/0.0

Výsledkem bude hodnota:

0/0:   Hex value: 0xFFC00000

Po převodu na binární tvar a rozdělení bitových polí:

Hexa Binárně s Exponent Mantisa
0xFFC00000 11111111110000000000000000000000 + 255 (spec) dva nejvyšší bity jednička

Opět nahlédněme do tabulky zveřejněné minule, abychom zjistili, co tato hodnota znamená:

s-bit exponent mantisa význam
0 255 >0 NaN – not a number
1 255 >0 NaN – not a number

Vidíme, že vydělením nuly nulou (ať již kladnou či zápornou) získáme speciální hodnotu NaN. Většina dalších instrukcí sice NaN může akceptovat, ale výsledkem operace bude opět NaN, na což je zapotřebí dávat při programování (nejenom v assembleru) pozor.

11. Třetí demonstrační příklad: dělení nulou

Dělení nenulové hodnoty kladnou i zápornou nulou jakož i dělení nuly nulou je ukázáno v dnešním třetím a současně i posledním demonstračním příkladu, jehož zdrojový kód je zobrazen pod tímto odstavcem. Ve všech třech případech se pro dělení používá instrukce FDIVP:

# asmsyntax=as

# Program pro otestovani deleni nulou
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky

.intel_syntax noprefix



# Nacteni definice makra pro ukonceni aplikace
.include "exit.s"

# Nacteni maker pro (opakovany) tisk zpravy i prislusne subrutiny
.include "writeMessage.s"

# Nacteni makra pro vytisteni hexadecimalni 32bitove hodnoty
# spolecne s makrem je nactena i prislusna subrutina
.include "printHexNumber.s"



#-----------------------------------------------------------------------------
.section .data
fpuDivideByZeroMessage:
        .string "1/0:   "                        # zprava
fpuDivideByZeroMessageLength = $ - fpuDivideByZeroMessage   # delka zpravy

fpuDivideByNegativeZeroMessage:
        .string "-1/0:  "                        # zprava
fpuDivideByNegativeZeroMessageLength = $ - fpuDivideByNegativeZeroMessage   # delka zpravy

fpuDivideZeroByZeroMessage:
        .string "0/0:   "                        # zprava
fpuDivideZeroByZeroMessageLength = $ - fpuDivideZeroByZeroMessage   # delka zpravy



#-----------------------------------------------------------------------------
.section .bss

.lcomm number, 4                     # na toto misto se bude ukladat konstanta typu float



#-----------------------------------------------------------------------------
.section .text
        .global _start               # tento symbol ma byt dostupny i linkeru

_start:
        writeMessage fpuDivideByZeroMessage, fpuDivideByZeroMessageLength

        fld1                         # nacteni FP konstanty 1.0
        fldz                         # nacteni FP konstanty 0.0
        fdivp                        # deleni nulou
        fstp dword ptr number        # ulozeni do pameti (4 bajty)
        mov  eax, dword ptr number   # nacteni hodnoty, tentokrat to celociselneho registru
        printHexNumber eax           # vytiskneme celociselnou hodnotu v hexa tvaru



        writeMessage fpuDivideByNegativeZeroMessage, fpuDivideByNegativeZeroMessageLength

        fld1                         # nacteni FP konstanty 1.0
        fldz                         # nacteni FP konstanty 0.0
        fchs                         # zmena znamenka nuly
        fdivp                        # deleni zapornou nulou
        fstp dword ptr number        # ulozeni do pameti (4 bajty)
        mov  eax, dword ptr number   # nacteni hodnoty, tentokrat to celociselneho registru
        printHexNumber eax           # vytiskneme celociselnou hodnotu v hexa tvaru



        writeMessage fpuDivideZeroByZeroMessage, fpuDivideZeroByZeroMessageLength

        fldz                         # nacteni FP konstanty 0.0
        fldz                         # nacteni FP konstanty 0.0
        fdivp                        # vypocet 0.0/0.0
        fstp dword ptr number        # ulozeni do pameti (4 bajty)
        mov  eax, dword ptr number   # nacteni hodnoty, tentokrat to celociselneho registru
        printHexNumber eax           # vytiskneme celociselnou hodnotu v hexa tvaru



        println                      # odradkovani

        exit                         # ukonceni aplikace

12. Pomocné zdrojové soubory

Demonstrační příklady popsané v předchozích kapitolách používají makra a subrutiny (procedury), s nimiž jsme se již seznámili minule a předminule. Tyto makra a subrutiny jsou uloženy v samostatných souborech přidávaných do hlavního programu s využitím direktivy .include.

exit.s

# asmsyntax=as

# Makro pro ukonceni procesu v Linuxu.
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky

sys_exit   = 1                       # cislo syscallu pro ukonceni procesu

# Deklarace makra pro ukonceni aplikace
.macro exit
        mov   eax, sys_exit          # cislo sycallu pro funkci "exit"
        mov   ebx, 0                 # exit code = 0
        int   0x80                   # volani Linuxoveho kernelu
.endm

writeMessage.s

# asmsyntax=as

# Makro pro tisk zpravy na standardni vystup.
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky

# Linux kernel system call table
sys_write  = 4
std_output = 1



# Deklarace makra pro vytisteni zpravy na standardni vystup
.macro writeMessage message,messageLength
        mov   ecx, offset \message   # adresa retezce, ktery se ma vytisknout
        mov   edx, \messageLength    # pocet znaku, ktere se maji vytisknout
        call  write_message          # vytisknout zpravu "Zero flag not set"
.endm



# Podprogram pro vytisteni zpravy na standardni vystup
# Ocekava se, ze v ecx bude adresa zpravy a v edx jeji delka
write_message:
        mov   eax, sys_write         # cislo syscallu pro funkci "write"
        mov   ebx, std_output        # standardni vystup
        int   0x80
        ret



# Deklarace makra pro vytisteni znaku konce radku (provede se tedy odradkovani)
.macro println
        writeMessage printlnMessage,printlnLength
.endm



#-----------------------------------------------------------------------------
.section .data
# Miniretezec pouzivany makrem println
printlnMessage:
        .string "\n"
printlnLength = $ - printlnMessage

printHexNumber.s (32bitová varianta)

# asmsyntax=as

# Makro pro pripravu a tisk hexadecimalni hodnoty na standardni vystup.
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky

.intel_syntax noprefix



# Makro pro vypis 32bitove hexadecimalni hodnoty na standardni vystup
# Jedinym parametrem makra je hodnota (konstanta)
.macro printHexNumber value
        pusha                              # uschovat vsechny registry na zasobnik
        mov  edx, \value                   # hodnotu pro tisk ulozit do registru EDX
        mov  ebx, offset hexValueTemplate  # adresu pro retezec ulozit do registru EBX
        call hex2string                    # zavolani prislusne subrutiny pro prevod na string
        writeMessage hexValueMessage, hexValueMessageLen # retezec je naplnen, tak ho muzeme vytisknout
        popa                               # obnovit obsah vsech registru
.endm



#-----------------------------------------------------------------------------
.section .data
hexValueMessage:
        .string "Hex value: 0x"             # prvni cast zpravy
hexValueTemplate:                           # druha cast zpravy ma vlastni navesti
        .string "????????\n"                # otazniky budou prepsany
hexValueMessageLen = $ - hexValueMessage    # delka zpravy



#-----------------------------------------------------------------------------
.section .text

# Subrutina urcena pro prevod 32bitove hexadecimalni hodnoty na retezec
# Vstup: EDX - hodnota, ktera se ma prevest na retezec
#        EBX - adresa jiz drive alokovaneho retezce (resp. osmice bajtu)
hex2string:
                  mov cl,  8                # pocet opakovani smycky

print_one_digit:  rol edx, 4                # rotace doleva znamena, ze se do spodnich 4 bitu nasune dalsi cifra
                  mov al, dl                # nechceme porusit obsah vstupni hodnoty v EDX, proto pouzijeme AL
                  and al, 0x0f              # maskovani, potrebujeme pracovat jen s jednou cifrou
                  cmp al, 10                # je cifra vetsi nebo rovna 10?
                  jl  store_digit           # neni, pouze prevest 0..9 na ASCII hodnotu '0'..'9'

alpha_digit:      add al, 'A'-10-'0'        # prevod hodnoty 10..15 na znaky 'A'..'F'

store_digit:      add al, '0'
                  mov byte ptr [ebx], al    # ulozeni cifry do retezce
                  inc ebx                   # dalsi ulozeni v retezci o znak dale
                  dec cl                    # snizeni pocitadla smycky
                  jnz print_one_digit       # a opakovani smycky, dokud se nedosahlo nuly

                  ret                       # navrat ze subrutiny

printHexNumber_64.s (64bitová varianta)

# asmsyntax=as

# Makro pro pripravu a tisk hexadecimalni hodnoty na standardni vystup.
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky

.intel_syntax noprefix



# Makro pro vypis 32bitove hexadecimalni hodnoty na standardni vystup
# Jedinym parametrem makra je hodnota (konstanta)
.macro printHexNumber value
        push rax
        push rbx
        push rcx
        push rdx                           # uschovat vsechny registry na zasobnik
        mov  edx, \value                   # hodnotu pro tisk ulozit do registru EDX
        mov  ebx, offset hexValueTemplate  # adresu pro retezec ulozit do registru EBX
        call hex2string                    # zavolani prislusne subrutiny pro prevod na string
        writeMessage hexValueMessage, hexValueMessageLen # retezec je naplnen, tak ho muzeme vytisknout
        pop rdx
        pop rcx
        pop rbx
        pop rax                            # obnovit obsah vsech registru
.endm



#-----------------------------------------------------------------------------
.section .data
hexValueMessage:
        .string "Hex value: 0x"             # prvni cast zpravy
hexValueTemplate:                           # druha cast zpravy ma vlastni navesti
        .string "????????\n"                # otazniky budou prepsany
hexValueMessageLen = $ - hexValueMessage    # delka zpravy



#-----------------------------------------------------------------------------
.section .text

# Subrutina urcena pro prevod 32bitove hexadecimalni hodnoty na retezec
# Vstup: EDX - hodnota, ktera se ma prevest na retezec
#        EBX - adresa jiz drive alokovaneho retezce (resp. osmice bajtu)
hex2string:
                  mov cl,  8                # pocet opakovani smycky

print_one_digit:  rol edx, 4                # rotace doleva znamena, ze se do spodnich 4 bitu nasune dalsi cifra
                  mov al, dl                # nechceme porusit obsah vstupni hodnoty v EDX, proto pouzijeme AL
                  and al, 0x0f              # maskovani, potrebujeme pracovat jen s jednou cifrou
                  cmp al, 10                # je cifra vetsi nebo rovna 10?
                  jl  store_digit           # neni, pouze prevest 0..9 na ASCII hodnotu '0'..'9'

alpha_digit:      add al, 'A'-10-'0'        # prevod hodnoty 10..15 na znaky 'A'..'F'

store_digit:      add al, '0'
                  mov byte ptr [ebx], al    # ulozeni cifry do retezce
                  inc ebx                   # dalsi ulozeni v retezci o znak dale
                  dec cl                    # snizeni pocitadla smycky
                  jnz print_one_digit       # a opakovani smycky, dokud se nedosahlo nuly

                  ret                       # navrat ze subrutiny

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

Všechny tři dnes popisované demontrační příklady byly, podobně jako ve všech předchozích částech tohoto seriálu, společně s podpůrnými skripty určenými pro jejich překlad či naopak pro disassembling, uloženy do GIT repositáře dostupného na adrese https://github.com/tisnik/presentations/. Všechny příklady jsou určeny pro GNU Assembler a používají Intel syntaxi, která je pro mnoho programátorů čitelnější, než původní AT&T syntaxe. Následují tabulky obsahující odkazy na zdrojové kódy příkladů i na již zmíněné podpůrné skripty:

První demonstrační příklad: vytištění hodnot 0.0, 1.0 a Pi v hexadecimálním tvaru

# Soubor Popis Odkaz do repositáře
1 main.s hlavní program pro GNU Assembler, varianta pro i386 https://github.com/tisnik/presentations/tree/master/assembler/40_fpu_constants/main.s
2 main_64bit.s hlavní program pro GNU Assembler, varianta pro x86-64 https://github.com/tisnik/presentations/tree/master/assembler/40_fpu_constants/main_64bit.s
3 exit.s program pro GNU Assembler, který se vkládá do prvního souboru https://github.com/tisnik/presentations/tree/master/assembler/40_fpu_constants/exit.s
4 writeMessage.s program pro GNU Assembler, který se vkládá do prvního souboru https://github.com/tisnik/presentations/tree/master/assembler/40_fpu_constants/writeMessage.s
5 printHexNumber.s 32bitová implementace makra a subrutiny pro převod hex2string https://github.com/tisnik/presentations/tree/master/assembler/40_fpu_constants/printHexNumber.s
6 printHexNumber_64bit.s 64bitová implementace makra a subrutiny pro převod hex2string https://github.com/tisnik/presentations/tree/master/assembler/40_fpu_constants/printHexNumber_64bit.s
7 assemble skript pro překlad na procesorech i386 https://github.com/tisnik/presentations/tree/master/assembler/40_fpu_constants/assemble
8 assemble_64bit skript pro překlad na procesorech x86-64 https://github.com/tisnik/presentations/tree/master/assembler/40_fpu_constants/assemble_64bit
9 disassemble skript pro disassembling https://github.com/tisnik/presentations/tree/master/assembler/40_fpu_constants/disassemble

Druhý demonstrační příklad: základní aritmetické operace

# Soubor Popis Odkaz do repositáře
1 main.s hlavní program pro GNU Assembler, varianta pro i386 https://github.com/tisnik/presentations/tree/master/assembler/41_fpu_arithmetic/main.s
2 main_64bit.s hlavní program pro GNU Assembler, varianta pro x86-64 https://github.com/tisnik/presentations/tree/master/assembler/41_fpu_arithmetic/main_64bit.s
3 exit.s program pro GNU Assembler, který se vkládá do prvního souboru https://github.com/tisnik/presentations/tree/master/assembler/41_fpu_arithmetic/exit.s
4 writeMessage.s program pro GNU Assembler, který se vkládá do prvního souboru https://github.com/tisnik/presentations/tree/master/assembler/41_fpu_arithmetic/writeMessage.s
5 printHexNumber.s 32bitová implementace makra a subrutiny pro převod hex2string https://github.com/tisnik/presentations/tree/master/assembler/41_fpu_arithmetic/printHexNumber.s
6 printHexNumber_64bit.s 64bitová implementace makra a subrutiny pro převod hex2string https://github.com/tisnik/presentations/tree/master/assembler/41_fpu_arithmetic/printHexNumber_64bit.s
7 assemble skript pro překlad na procesorech i386 https://github.com/tisnik/presentations/tree/master/assembler/41_fpu_arithmetic/assemble
8 assemble_64bit skript pro překlad na procesorech x86-64 https://github.com/tisnik/presentations/tree/master/assembler/41_fpu_arithmetic/assemble_64bit
9 disassemble skript pro disassembling https://github.com/tisnik/presentations/tree/master/assembler/41_fpu_arithmetic/disassemble

Třetí demonstrační příklad: dělení nulou

# Soubor Popis Odkaz do repositáře
1 main.s hlavní program pro GNU Assembler, varianta pro i386 https://github.com/tisnik/presentations/tree/master/assembler/42_fpu_div_by_0/main.s
2 main_64bit.s hlavní program pro GNU Assembler, varianta pro x86-64 https://github.com/tisnik/presentations/tree/master/assembler/42_fpu_div_by_0/main_64bit.s
3 exit.s program pro GNU Assembler, který se vkládá do prvního souboru https://github.com/tisnik/presentations/tree/master/assembler/42_fpu_div_by_0/exit.s
4 writeMessage.s program pro GNU Assembler, který se vkládá do prvního souboru https://github.com/tisnik/presentations/tree/master/assembler/42_fpu_div_by_0/writeMessage.s
5 printHexNumber.s 32bitová implementace makra a subrutiny pro převod hex2string https://github.com/tisnik/presentations/tree/master/assembler/42_fpu_div_by_0/printHexNumber.s
6 printHexNumber_64bit.s 64bitová implementace makra a subrutiny pro převod hex2string https://github.com/tisnik/presentations/tree/master/assembler/42_fpu_div_by_0/printHexNumber_64bit.s
7 assemble skript pro překlad na procesorech i386 https://github.com/tisnik/presentations/tree/master/assembler/42_fpu_div_by_0/assemble
8 assemble_64bit skript pro překlad na procesorech x86-64 https://github.com/tisnik/presentations/tree/master/assembler/42_fpu_div_by_0/assemble_64bit
9 disassemble skript pro disassembling https://github.com/tisnik/presentations/tree/master/assembler/42_fpu_div_by_0/disassemble

14. Odkazy na Internetu

  1. Simply FPU
    http://www.website.masmforum.com/tutorials/fptute/
  2. Art of Assembly language programming: The 80x87 Floating Point Coprocessors
    https://courses.engr.illinois.edu/ece390/books/artofasm/CH14/CH14-3.html
  3. Art of Assembly language programming: The FPU Instruction Set
    https://courses.engr.illinois.edu/ece390/books/artofasm/CH14/CH14-4.html
  4. INTEL 80387 PROGRAMMER'S REFERENCE MANUAL
    http://www.ragestorm.net/downloads/387intel.txt
  5. x86 Instruction Set Reference: FLD
    http://x86.renejeschke.de/html/file_module_x86_id_100.html
  6. x86 Instruction Set Reference: FLD1/FLDL2T/FLDL2E/FLDPI/FLDLG2/FLDLN2/FLDZ
    http://x86.renejeschke.de/html/file_module_x86_id_101.html
  7. x86 Instruction Set Reference: FLD
    http://x86.renejeschke.de/html/file_module_x86_id_100.html
  8. x86 Instruction Set Reference: FST/FSTP
    http://x86.renejeschke.de/html/file_module_x86_id_117.html
  9. x86 Instruction Set Reference: FADD/FADDP/FIADD
    http://x86.renejeschke.de/html/file_module_x86_id_81.html
  10. x86 Instruction Set Reference: FSUB/FSUBP/FISUB
    http://x86.renejeschke.de/html/file_module_x86_id_121.html
  11. x86 Instruction Set Reference: FDIV/FDIVP/FIDIV
    http://x86.renejeschke.de/html/file_module_x86_id_91.html
  12. x86 Instruction Set Reference: BT
    http://x86.renejeschke.de/html/file_module_x86_id_22.html
  13. x86 Instruction Set Reference: BTC
    http://x86.renejeschke.de/html/file_module_x86_id_23.html
  14. x86 Instruction Set Reference: BTR
    http://x86.renejeschke.de/html/file_module_x86_id_24.html
  15. x86 Instruction Set Reference: BTS
    http://x86.renejeschke.de/html/file_module_x86_id_25.html
  16. x86 Instruction Set Reference: BSF
    http://x86.renejeschke.de/html/file_module_x86_id_19.html
  17. x86 Instruction Set Reference: BSR
    http://x86.renejeschke.de/html/file_module_x86_id_20.html
  18. x86 Instruction Set Reference: BSWAP
    http://x86.renejeschke.de/html/file_module_x86_id_21.html
  19. x86 Instruction Set Reference: XCHG
    http://x86.renejeschke.de/html/file_module_x86_id_328.html
  20. x86 Instruction Set Reference: SETcc
    http://x86.renejeschke.de/html/file_module_x86_id_288.html
  21. X86 Assembly/Arithmetic
    https://en.wikibooks.org/wiki/X86_Assembly/Arithmetic
  22. Art of Assembly - Arithmetic Instructions
    http://oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter_6/CH06-2.html
  23. The GNU Assembler Tutorial
    http://tigcc.ticalc.org/doc/gnuasm.html
  24. The GNU Assembler - macros
    http://tigcc.ticalc.org/doc/gnuasm.html#SEC109
  25. ARM subroutines & program stack
    http://www.toves.org/books/armsub/
  26. Generating Mixed Source and Assembly List using GCC
    http://www.systutorials.com/240/generate-a-mixed-source-and-assembly-listing-using-gcc/
  27. Calling subroutines
    http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.kui0100a/armasm_cihcfigg.htm
  28. ARM Assembly Language Programming
    http://peter-cockerell.net/aalp/html/frames.html
  29. ASM Flags
    http://www.cavestory.org/guides/csasm/guide/asm_flags.html
  30. Status Register
    https://en.wikipedia.org/wiki/Status_register
  31. Intel x86 JUMP quick reference
    http://unixwiz.net/techtips/x86-jumps.html
  32. Linux assemblers: A comparison of GAS and NASM
    http://www.ibm.com/developerworks/library/l-gas-nasm/index.html
  33. Programovani v assembleru na OS Linux
    http://www.cs.vsb.cz/grygarek/asm/asmlinux.html
  34. Is it worthwhile to learn x86 assembly language today?
    https://www.quora.com/Is-it-worthwhile-to-learn-x86-assembly-language-today?share=1
  35. Why Learn Assembly Language?
    http://www.codeproject.com/Articles/89460/Why-Learn-Assembly-Language
  36. Is Assembly still relevant?
    http://programmers.stackexchange.com/questions/95836/is-assembly-still-relevant
  37. Why Learning Assembly Language Is Still a Good Idea
    http://www.onlamp.com/pub/a/onlamp/2004/05/06/writegreatcode.html
  38. Assembly language today
    http://beust.com/weblog/2004/06/23/assembly-language-today/
  39. Assembler: Význam assembleru dnes
    http://www.builder.cz/rubriky/assembler/vyznam-assembleru-dnes-155960cz
  40. Assembler pod Linuxem
    http://phoenix.inf.upol.cz/linux/prog/asm.html
  41. AT&T Syntax versus Intel Syntax
    https://www.sourceware.org/binutils/docs-2.12/as.info/i386-Syntax.html
  42. Linux Assembly website
    http://asm.sourceforge.net/
  43. Using Assembly Language in Linux
    http://asm.sourceforge.net/articles/linasm.html
  44. vasm
    http://sun.hasenbraten.de/vasm/
  45. vasm – dokumentace
    http://sun.hasenbraten.de/vasm/release/vasm.html
  46. The Yasm Modular Assembler Project
    http://yasm.tortall.net/
  47. 680x0:AsmOne
    http://www.amigacoding.com/index.php/680x0:AsmOne
  48. ASM-One Macro Assembler
    http://en.wikipedia.org/wiki/ASM-One_Macro_Assembler
  49. ASM-One pages
    http://www.theflamearrows.info/documents/asmone.html
  50. Základní informace o ASM-One
    http://www.theflamearrows.info/documents/asminfo.html
  51. Linux Syscall Reference
    http://syscalls.kernelgrok.com/
  52. Programming from the Ground Up Book - Summary
    http://savannah.nongnu.org/projects/pgubook/
  53. IBM System 360/370 Compiler and Historical Documentation
    http://www.edelweb.fr/Simula/
  54. IBM 700/7000 series
    http://en.wikipedia.org/wiki/IBM_700/7000_series
  55. IBM System/360
    http://en.wikipedia.org/wiki/IBM_System/360
  56. IBM System/370
    http://en.wikipedia.org/wiki/IBM_System/370
  57. Mainframe family tree and chronology
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_FT1.html
  58. 704 Data Processing System
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_PP704.html
  59. 705 Data Processing System
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_PP705.html
  60. The IBM 704
    http://www.columbia.edu/acis/history/704.html
  61. IBM Mainframe album
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_album.html
  62. Osmibitové muzeum
    http://osmi.tarbik.com/
  63. Tesla PMI-80
    http://osmi.tarbik.com/cssr/pmi80.html
  64. PMI-80
    http://en.wikipedia.org/wiki/PMI-80
  65. PMI-80
    http://www.old-computers.com/museum/computer.asp?st=1&c=1016
  66. The 6502 overflow flag explained mathematically
    http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html
  67. X86 Opcode and Instruction Reference
    http://ref.x86asm.net/coder