Specifické vlastnosti procesorů AArch64: aritmetické operace

24. 3. 2022
Doba čtení: 37 minut

Sdílet

 Autor: Raspberry Pi Foundation
Dnes se budeme zabývat instrukcemi určenými pro provádění aritmetických operací. Tyto instrukce se rozdělují na instrukce určené pro celočíselné datové typy a na instrukce, které provádějí operace s hodnotami s plovoucí řádovou čárkou.

Obsah

1. Aritmetické operace s celočíselnými typy i s hodnotami s plovoucí řádovou čárkou

2. Specifické vlastnosti aritmeticko-logické jednotky

3. Hodnoty typu single a double

4. Formát plovoucí řádové binární tečky a norma IEEE 754

5. Operace s hodnotami typu single a double

6. Základní operace s FP hodnotami – načtení konstanty do FP registru

7. Praktický příklad – načtení konstant do registrů D1S1

8. Nepodporované konstanty aneb (logické) omezení možností RISCových instrukcí

9. Vynulování registrů Dx a Sx

10. Nepřímé načtení konstant s libovolnou hodnotou do registrů DxSx

11. Přenos operandů mezi registry

12. Ukázka přenosu mezi celočíselným a FP registrem

13. Konverze mezi různými formáty

14. Převod FP hodnot na celá čísla (zaokrouhlení)

15. Základní aritmetické operace s hodnotami s plovoucí řádovou čárkou

16. Porovnání operandů s hodnotami s plovoucí řádovou čárkou

17. SIMD operace

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

19. Předchozí články o architektuře AArch64

20. Odkazy na Internetu

1. Aritmetické operace s celočíselnými typy i s hodnotami s plovoucí řádovou čárkou

Nová instrukční sada mikroprocesorů s architekturou AArch64 obsahuje instrukce, které je možné rozdělit do několika oblastí podle toho, jaká jednotka implementovaná uvnitř mikroprocesoru tyto instrukce skutečně spouští. Podívejme se na následující tabulku:

Skupina Další dělení
Load-Store Load-Store pro jeden registr
  Load-Store pro dvojici registrů (již jsme si ukázali)
  Prefetch
   
Skoky Nepodmíněné skoky
  Nepodmíněný skok na adresu v registru (popsáno)
  Podmíněné skoky (popsáno)
   
ALU operace Základní aritmetické instrukce
  Násobení a dělení
  Logické instrukce
  Znaménkové rozšíření operandu či rozšíření o nuly
  Bitové operace
  Extrakce dat
  Bitové posuny
  Aritmetické posuny
  Podmíněné zpracování dat (popsáno)
  Podmíněné porovnání
   
FP operace Přenos operandů mezi registry
  Konverze mezi různými formáty
  Převod na celá čísla (zaokrouhlení)
  Základní aritmetické operace
  Výpočet minima a maxima
  MAC (Multiply Accumulate)
  Porovnání operandů
  Podmíněný výběr operandu
   
SIMD operace Aritmetické operace se skaláry
  Aritmetické operace s vektory
  Permutace vektorů
  Konverze dat
  Instrukce z crypto extension (patří do SIMD)
   
Systémové instrukce Zpracování výjimek
  Přístup k systémovým registrům
  Implementace bariér
  Instrukce pro jádro systému

Ve čtvrté části miniseriálu o specifických vlastnostech mikroprocesorů s architekturou AArch64 se budeme zabývat převážně instrukcemi určenými pro provádění aritmetických operací. Tyto instrukce se jak z historických tak i technických důvodů rozdělují na instrukce určené pro celočíselné datové typy (bajt, 16bitové slovo, 32bitové slovo, 64bitové slovo) a na instrukce, které provádějí operace s hodnotami s plovoucí řádovou čárkou (tedy ponejvíce s hodnotami single a double, i když se v této oblasti nově objevil například formát blfoat). Další dělení je možné podle toho, zda instrukce prování operaci s jedinou dvojicí operandů, nebo s dvojicí vektorů (ovšem sada registrů zůstává v tomto případě pořád stejná – konkrétně se jedná o registry V0V31,).

2. Specifické vlastnosti aritmeticko-logické jednotky

Kromě toho, že aritmeticko-logická jednotka byla (v porovnání s původní architekturou ARM neboli dnes A32 a T32) rozšířena pro zpracování 64bitových operandů u prakticky všech instrukcí, došlo k jejímu doplnění o vylepšenou násobičku a děličku. Násobička může kromě běžných operací pro násobení provádět i operace typu MAC (Multiply Accumulate), které typicky najdeme u DSP. U AArchu64 je zde ovšem jedna podstatná změna – namísto akumulátoru se může použít odlišný vstupní a odlišný výstupní registr, což konkrétně znamená, že DSP operace:

acc += op2 × op3

dokáže AArch64 provést:

op1 = op2 × op3 + op4

Podívejme se nyní, které instrukce provádí násobička a dělička:

# Instrukce Stručný popis
1 MUL 32bitové či 64bitové násobení
2 MADD výsledek = op2 × op3 + op4
3 MSUB výsledek = op4 – op2 × op3
4 MNEG výsledek = – op2 × op3
     
5 SMULL násobení hodnot se znaménkem (32×32 → 64)
6 SMADDL MADD hodnot se znaménkem pro (32×32 → 64)
7 SMSUBL MSUB hodnot se znaménkem pro (32×32 → 64)
8 SMNEGL MNEG hodnot se znaménkem pro (32×32 → 64)
9 SMULH násobení 64×64, z výsledku se vezme jen horních 64 bitů ze 128
     
10 UMULL násobení hodnot bez znaménka (32×32 → 64)
11 UMADDL MADD hodnot bez znaménka pro (32×32 → 64)
12 UMSUBL MSUB hodnot bez znaménka pro (32×32 → 64)
13 UMNEGL MNEG hodnot bez znaménka pro (32×32 → 64)
14 UMULH násobení 64×64, z výsledku se vezme jen horních 64 bitů ze 128
     
15 SDIV 32bitové či 64bitové dělení hodnot se znaménkem
16 UDIV 32bitové či 64bitové dělení hodnot bez znaménka
Poznámka: ve skutečnosti je instrukce MUL, tedy z pohledu programátora „běžné násobení“ aliasem pro instrukci MADD, v níž je třetím vstupním operandem registr WZR či XZR, tedy „konstantní nula“. Prakticky totéž platí pro instrukci MNEG, která vznikla z instrukce MSUB, u níž je opět posledním vstupním operandem nulový registr.
Operace Vstupní operandy Výsledek
MUL 32×32 bitů 32 bitů
MUL 64×64 bitů 64 bitů
MUL 32×32 bitů 64 bitů (rozšíření)
MUL 64×64 bitů horních 64 bitů (rozšíření)
     
MAC 32±32×32 bitů 32 bitů
MAC 64±64×64 bitů 64 bitů
MAC 64±32×32 bitů 64 bitů (rozšíření)
Poznámka: se všemi výše zmíněnými instrukcemi se ještě jednou setkáme příště; jejich praktické studium totiž již vyžaduje znalost práce s debuggerem.

V navazujících kapitolách se zaměříme na registry a instrukce určené pro zpracování numerických hodnot s plovoucí řádovou čárkou.

3. Hodnoty typu single a double

Uložení racionálních čísel ve formátu plovoucí řádové tečky (někdy se taktéž setkáme s označením „FP formát“) se od celočíselného formátu nebo formátu s pevnou řádovou tečkou (v ČR spíše řádovou čárkou) odlišuje především v tom, že si každá numerická hodnota sama v sobě nese aktuální polohu řádové tečky (zatímco v případě, že je tečka/čárka pevně nastavena, je tato informace součástí programu a nikoli hodnoty). Z tohoto důvodu je kromě bitů, které musí být rezervovány pro uložení významných číslic numerické hodnoty, nutné pro každou numerickou hodnotu rezervovat i další bity, v nichž je určena mocnina o nějakém základu (typicky 2, 8, 10 či 16), kterou musí být významné číslice vynásobeny resp. vyděleny. První část čísla uloženého v FP formátu se nazývá mantisa, druhá část exponent (navíc se ještě přidává informace o znaménku). Obecný formát uložení a způsob získání původního čísla je následující:

xFP=be×m

přičemž význam jednotlivých symbolů je následující:

  1. xFX značí reprezentovanou numerickou hodnotu z podmnožiny reálných čísel
  2. b je báze, někdy také nazývaná radix
  3. e je hodnota exponentu (může být i záporná)
  4. m je mantisa, která může být i záporná
Poznámka: většinou požadujeme i práci se zápornými hodnotami, proto se zavádí další bit s pro uložení znaménka. To mj. znamená, že lze reprezentovat kladnou i zápornou nulu, což lze považovat za výhodu – je třeba velký rozdíl v tom dělit kladnou nulou či nulou zápornou.

Konkrétní formát numerických hodnot reprezentovaných v systému plovoucí řádové tečky závisí především na volbě báze (radixu) a také na počtu bitů rezervovaných pro uložení mantisy a exponentu. V minulosti existovalo značné množství různých formátů plovoucí řádové tečky (vzpomíná si někdo například na Turbo Pascal s jeho šestibajtovým datovým typem real?), v relativně nedávné minulosti se však ustálilo použití formátů specifikovaných v normě IEEE 754 (ta sama je ovšem postupně rozšiřována). Ovšem, jak uvidíme dále, se ukazuje, že původní formáty definované v IEEE 754 nedostačují všem požadavkům, a to na obou stranách spektra (někdo požaduje vyšší přesnost/rozsah, jiný zase rychlost výpočtů a malé paměťové nároky). Proto došlo k rozšíření této normy o nové formáty a nezávisle na tom i na vývoji formátu bfloat16. Nicméně nás dnes budou v souvislosti s procesory AArch64 zajímat především formáty s jednoduchou a dvojitou přesností, neboli single (float) a double.

4. Formát plovoucí řádové binární tečky a norma IEEE 754

V oblasti FP formátů se dnes nejčastěji setkáme s výše zmíněnou normou IEEE 754, popř. jejími rozšířenými variantami. Norma IEEE 754 je velmi užitečná v tom, že specifikuje nejenom vlastní formát uložení numerických hodnot v systému plovoucí řádové tečky, ale (a to je celkem neznámá skutečnost) i pravidla implementace operací s těmito hodnotami, včetně konverzí. Konkrétně je v této normě popsáno:

  1. Základní (basic) a rozšířený (extended) formát uložení numerických hodnot.
  2. Způsob provádění základních matematických operací:
    • součet
    • rozdíl
    • součin
    • podíl
    • zbytek po dělení
    • druhá odmocnina
    • porovnání
  3. Režimy zaokrouhlování.
  4. Způsob práce s takzvanými denormalizovanými hodnotami.
  5. Pravidla konverze mezi celočíselnými formáty (integer bez a se znaménkem) a formáty s plovoucí řádovou čárkou.
  6. Způsob konverze mezi různými formáty s plovoucí řádovou čárkou (singledouble atd.).
  7. Způsob konverze základního formátu s plovoucí řádovou čárkou na řetězec číslic (včetně nekonečen a nečíselných hodnot).
  8. Práce s hodnotami NaN (not a number) a výjimkami, které mohou při výpočtech za určitých předpokladů vzniknout (NaN totiž ve skutečnosti jsou čísla :-).

Obrázek 1: První čip, který používal formát definovaný v IEEE 754 – Intel 8087.
Zdroj: Wikipedia, Autor: Dirk Oppelt

V normě (přesněji řečeno v její rozšířené variantě IEEE 754–2008, resp. její poslední úpravě IEEE 754–2019) nalezneme mj. i tyto FP formáty:

Označení Šířka (b) Báze Exponent (b) Mantisa (b)
IEEE 754 half 16 2 5 10+1
         
IEEE 754 single 32 2 8 23+1
IEEE 754 double 64 2 11 52+1
         
IEEE 754 double extended 80 2 15 64
IEEE 754 quadruple 128 2 15 112+1
IEEE 754 octuple 256 2 19 236+1
foobar

Obrázek 2: Mikroprocesory Pentium i všechny další čipy řady 80×86 již implicitně obsahují plnohodnotný FPU. Zlé jazyky tvrdí, že u první řady Pentií byl FPU tak rychlý jen proto, že výsledky pouze odhadoval :-)

Nás však budou v dalším textu zajímat především formáty single a double.

Typ single (nebo float, popř. float32) vypadá takto:

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

Exponent je přitom posunutý o hodnotu bias, která je nastavena na 127, protože je použit výše uvedený vztah:

bias=2eb-1-1

a po dosazení eb=8 (bitů) dostaneme:

bias=28–1-1=27-1=128–1=127

Vzorec pro vyjádření reálné hodnoty vypadá následovně:

Xsingle=(-1)s × 2exp-127 × m

Rozsah hodnot, které je možné reprezentovat ve formátu jednoduché přesnosti v normalizovaném tvaru je –3,4×1038 až 3,4×1038. Nejnižší reprezentovatelná (normalizovaná) hodnota je rovna 1,17549×10-38, denormalizovaná pak 1,40129×10-45. Jak jsme k těmto hodnotám došli? Zkuste se podívat na následující vztahy:

hexadecimální hodnota výpočet FP dekadický výsledek normalizováno
0×00000001 2-126×2-23 1,40129×10-45 ne
0×00800000 2-126 1,17549×10-38 ano
0×7F7FFFFF (2–2-23)×2127 3,4×1038 ano

Formát s dvojitou přesností (double), který je definovaný taktéž normou IEEE 754, se v mnoha ohledech podobá formátu s jednoduchou přesností (single), pouze se zdvojnásobil celkový počet bitů, ve kterých je hodnota uložena, tj. místo 32 bitů se používá plných 64 bitů:

bit 63 62 … 52 51 … 0
význam s exponent (11 bitů) mantisa (52 bitů)

Exponent je v tomto případě posunutý o hodnotu bias=2047 a vzorec pro výpočet reálné hodnoty vypadá takto:

Xdouble=(-1)s × 2exp-2047 × m

Přičemž hodnotu mantisy je možné pro normalizované hodnoty získat pomocí vztahu:

m=1+m51-1+m50-2+m49-3+…+m0-52

(mx představuje x-tý bit mantisy)

Rozsah hodnot ukládaných ve dvojité přesnosti je –1,7×10308..1,7×10308, nejmenší možná nenulová hodnota je rovna 2,2×10-308.

V novější normě IEEE 754–2008 je specifikován nepovinný formát nazvaný binary128, který se ovšem běžně označuje quadruple precision či jen quad precision. Tento formát je založen na slovech širokých 128 bitů (16 bajtů), která jsou rozdělena takto:

bit 127 126 … 112 111 … 0
význam s exponent (15 bitů) mantisa (112 bitů)

Exponent je v tomto případě posunutý o hodnotu bias=16383. Dekadická přesnost u tohoto formátu dosahuje 34 cifer!

Jen krátce se zmiňme o poslední variantě FP formátu, který se nazývá binary256 či méně formálně octuple precision. Tento formát využívá slova o šířce plných 256 bitů (32 bajtů) s následujícím rozdělením:

bit 255 254 … 236 235 … 0
význam s exponent (19 bitů) mantisa (235 bitů)

Exponent je v tomto případě posunutý o hodnotu bias=262143. Dekadická přesnost u tohoto formátu dosahuje 71 cifer, nejmenší (nenormalizovaná) reprezentovatelná hodnota rozdílná od nuly je přibližně 10−78984, maximální hodnota pak 1.611 ×1078913 (těžko říct, zda je takový rozsah vůbec reálně využitelný).

5. Operace s hodnotami typu single a double

Matematický koprocesor je sice u architektury AArch64 volitelný (u desktopových procesorů ho najdete vždy), ale oproti 32bitovým ARMům došlo k určitému zjednodušení – už neexistuje rozdělení ABI na soft floating point a hard floating point, protože pro předávání hodnot typu single/float a double jsou vždy použity FP registry popsané v navazující kapitole. Z technologického hlediska sice není soft floating point špatné řešení, ale prakticky způsobovalo (a dodnes způsobuje) množství problémů při distribuci knihoven i aplikací.

Samostatná sada pracovních registrů je používána při operacích s typy single/float a double (tedy s operandy reprezentovanými v systému plovoucí řádové čárky), u SIMD operací a taktéž kryptografickým modulem:

Jméno Význam
v0..v31 128bitové registry
d0..d31 spodních 64 bitů registrů v0..v31, použito pro hodnoty typu double
s0..s31 spodních 32 bitů registrů v0..v31, použito pro hodnoty typu single/float

Pro SIMD operace, tj. operace pracující s vektory, se výše uvedené registry Vn rozdělují následujícím způsobem:

Tvar (shape) Celkem Pojmenování v assembleru
8b×8 64b Vn.8B
8b×16 128b Vn.16B
16b×4 64b Vn.4H
16b×8 128b Vn.8H
32b×2 64b Vn.2S
32b×4 128b Vn.4S
64b×1 64b Vn.1D
64b×2 128b Vn.2D
Poznámka: Povšimněte si, že – na rozdíl od mnoha jiných architektur – nedochází k tomu, že by se například dva single registry mapovaly do jednoho double registru atd.
Poznámka2: většina instrukcí známých ze sady VFP (ARM32) byla přejmenována, ovšem prakticky každá z původních instrukcí má svůj nový ekvivalent. Jen pro upřesnění si vypišme některé původní instrukce VFP:

Aritmetické operace:

# Instrukce Význam Prováděný výpočet
1 VADD Fd, Fn, Fm součet Fd := Fn + Fm
2 VSUB Fd, Fn, Fm rozdíl Fd := Fn – Fm
3 VNEG Fd, Fm změna znaménka Fd := – Fm
4 VABS Fd, Fm absolutní hodnota Fd := abs(Fm)
5 VSQRT Fd, Fm druhá odmocnina Fd := sqrt(Fm)
6 VDIV Fd, Fn, Fm dělení Fd := Fn / Fm
7 VMUL Fd, Fn, Fm násobení Fd := Fn * Fm
8 VMLA Fd, Fn, Fm násobení + akumulace Fd := Fd + (Fn * Fm)
9 VMLS Fd, Fn, Fm odečtení součinu Fd := Fd – (Fn * Fm)
10 VNMUL Fd, Fn, Fm násobení + změna znaménka Fn := – (Fn * Fm)
11 VNMLA Fd, Fn, Fm kombinace VNMUL a VMLA Fd := – Fd – (Fn * Fm)
12 VNMLS Fd, Fn, Fm kombinace VNMUL a VMLS Fd := – Fd + (Fn * Fm)

Porovnání:

# Instrukce Význam Prováděný výpočet
1 VCMP Fd, Fm Porovnání obsahu dvou registrů Fd – Fm
2 VCMP Fd, #0.0 Porovnání jednoho registru s nulou Fd – 0.0

Přesuny dat:

# Instrukce Význam
1 VCVT{C}.F64.F32 Dd, Sm Konverze single na double
2 VCVT{C}.F32.F64 Sd, Dm Konverze double na single
3 VCVT{C}.F32/F64.U32 Fd, Sm Konverze unsigned integer na float
4 VCVT{C}.F32/F64.S32 Fd, Sm Konverze signed integer na float
5 VCVT{R}{C}.U32.F32/F64 Sd, Fm Konverze float na unsigned integer
6 VCVT{R}{C}.S32.F32/F64 Sd, Fm Konverze float na signed integer
7 VCVT.F32/F64.typ Fd, Fd, #bitů Konverze fixed-point na float (volitelná pozice tečky)
8 VCVT.typ.F32/F64 Fd, Fd, #bitů Konverze float na fixed-point (volitelná pozice tečky)
9 VCVTT.F16.F32 Sd,Sm Konverze single na half (do horních 16 bitů registru)
10 VCVTB.F16.F32 Sd,Sm Konverze single na half (do spodních 16 bitů registru)
11 VCVTT.F32.F16 Sd,Sm Konverze half na single
12 VCVTB.F32.F16 Sd,Sm Konverze half na single
13 VMOV.F32/F64 Fd, Fm Fd := Fm (prostá kopie)
14 VMOV Sn, Rd Sn := Rd (Rd = registr ARM procesoru)
15 VMOV Rd, Sn Rd := Sn (Rd = registr ARM procesoru)
16 VMOV Sn, Sm, Rd, Rn Sn := Rd, Sm := Rn (kopie dvou registrů)
17 VMOV Rd, Rn, Sn, Sm Rd := Sn, Rn := Sm (kopie dvou registrů)
18 VMOV Dm, Rd, Rn Dm[31:0] := Rd, Dm[63:32] := Rn (pro double jsou zapotřebí dva ARM registry)
19 VMOV Rd, Rn, Dm Rd := Dm[31:0], Rn := Dm[63:32] (pro double jsou zapotřebí dva ARM registry)
20 VMOV Dn[0], Rd Dn[31:0] := Rd (pouze spodní polovina double)
21 VMOV Rd, Dn[0] Rd := Dn[31:0] (pouze spodní polovina double)
22 VMOV Dn[1], Rd Dn[63:32] := Rd (pouze horní polovina double)
23 VMOV Rd, Dn[1] Rd := Dn[63:32] (pouze horní polovina double)
24 VMRS APSR_nzcv, FPSCR APSR flags := FPSCR flags (přenos příznaků)

6. Základní operace s FP hodnotami – načtení konstanty do FP registru

Pro načtení konstanty typu single/float a double do jednoho z pracovních registrů Sx či Dx se používá instrukce nazvaná FMOV. Ovšem vzhledem k tomu, že jak instrukční slovo, tak i konstanta mají dohromady pouhých 32 bitů (jako všechny ostatní RISCové instrukce), je zřejmé, že tímto způsobem není možné načíst libovolné číslo, ale pouze hodnotu odpovídající určitým pravidlům. Reprezentovatelná hodnota odpovídá výrazu ±n÷16×2r, kde n je celé číslo 16 ≤ n ≤ 31 a r je taktéž celé číslo –3 ≤ r ≤ 4. Tato čísla jsou reprezentována čtyřmi resp. třemi bity, další bit slouží pro uložení znaménka v instrukčním slovu (k tomuto omezení se ještě vrátíme):

# Instrukce Stručný popis
1 FMOV Sd, #fpimm načtení konstanty typu single/float do registru Sx
2 FMOV Dd, #fpimm načtení konstanty typu double do registru Dx

Speciálním případem je načtení nuly, které se provede jednoduše – použitím registrů XZR či WZR, které obsahují nulu a konstanta nula (0,0) je ve formátu IEEE 754 taktéž reprezentována samými nulovými bity (což je jedna z mnoha vychytávek IEEE 754).

Následující úryvek céčkového kódu:

float  x = 0.0;
double y = 0.0;

se přeloží následovně (jedná se o lokální proměnné ukládané na zásobníkový rámec, tedy relativně vůči SP):

// float x = 0.0
str  wzr, [sp, 28]
 
// double y = 0.0
str  xzr, [sp, 16]

Další instrukce slouží pro načtení operandu z paměti a pro uložení operandů zpět do paměti. Tyto instrukce již známe, pouze došlo k jejich rozšíření i pro použití s FP registry. Operace s jednotlivými bajty se používají u vektorových operací. Samozřejmě nesmíme zapomenout ani na instrukce pro načtení a uložení registrového páru:

# Instrukce Stručný popis
1 LDR Bt, adresa načtení spodních osmi bitů
2 LDR Ht, adresa načtení spodních šestnácti bitů
3 LDR St, adresa načtení 32 bitů (float)
4 LDR Dt, adresa načtení 64 bitů (double)
5 LDR Qt, adresa načtení 128 bitů (quad)
     
6 STR Bt, adresa uložení spodních osmi bitů
7 STR Ht, adresa uložení spodních šestnácti bitů
8 STR St, adresa uložení 32 bitů (float)
9 STR Dt, adresa uložení 64 bitů (double)
10 STR Qt, adresa uložení 128 bitů (quad)
     
11 LDP S1, S2, adresa načtení registrového páru (single)
12 LDP D1, D2, adresa načtení registrového páru (double)
13 LDP Q1, Q2, adresa načtení registrového páru (quad)
     
14 STP S1, S2, adresa uložení registrového páru (single)
15 STP D1, D2, adresa uložení registrového páru (double)
16 STP Q1, Q2, adresa uložení registrového páru (quad)

Podívejme se opět na jednoduchý příklad využití těchto instrukcí v praxi. Následující fragment céčkového kódu s inicializací čtyř lokálních proměnných:

float  x = 1.0;
float  y = 10.0;
float  z = 100.0;
float  w = 1000.0;

se přeloží takto:

// float  x = 1.0;
fmov    s0, 1.0e+0
str     s0, [sp, 12]
 
// float  y = 10.0;
fmov    s0, 1.0e+1
str     s0, [sp, 8]
 
// float  z = 100.0;
adrp    x0, .LC0
add     x0, x0, :lo12:.LC0
ldr     s0, [x0]
str     s0, [sp, 4]
 
// float  w = 1000.0;
adrp    x0, .LC1
add     x0, x0, :lo12:.LC1
ldr     s0, [x0]
str     s0, [sp]

První proměnné lze načíst přímo instrukcí FMOV (konstanta je součástí instrukce), další pouze nepřímo z operační paměti.

Konstanty uložené v operační paměti:

.LC0:
        .word   1120403456
.LC1:
        .word   1148846080

7. Praktický příklad – načtení konstant do registrů D1S1

Použití instrukcí FMOV pro načtení konstanty je snadné v případě, že je konstantu možné uložit přímo do instrukčního slova. Nejdříve načteme konstantu 1.0 do registru D1 (typu double):

# asmsyntax=as
 
# Načtení FP konstanty do registru d1
# v assembleru GNU AS pro architekturu AArch64.
#
# Autor: Pavel Tišnovský
 
 
 
# Linux kernel system call table
sys_exit=93
 
# List of syscalls for AArch64:
# https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h
 
 
 
#-----------------------------------------------------------------------------
.section .data
 
 
 
#-----------------------------------------------------------------------------
.section .bss
 
 
 
#-----------------------------------------------------------------------------
.section .text
        .global _start          // tento symbol má být dostupný i z linkeru
 
_start:
        fmov d1, #1.00          // načtení konstanty do registru
 
        mov  x8, #sys_exit      // číslo sycallu pro funkci "exit"
        mov  x0, #0             // exit code = 0
        svc  0                  // volání Linuxového kernelu

Obsah výsledného binárního souboru po překladu a slinkování:

$ objdump -d a.out
 
a.out:     file format elf64-littleaarch64
 
 
Disassembly of section .text:
 
0000000000400078 <_start>:
  400078:       1e6e1001        fmov    d1, #1.000000000000000000e+00
  40007c:       d2800ba8        mov     x8, #0x5d                       // #93
  400080:       d2800000        mov     x0, #0x0                        // #0
  400084:       d4000001        svc     #0x0

Podobný příklad, ovšem pro registr S1 a tudíž konstantu typu single:

# asmsyntax=as
 
# Načtení FP konstanty do registru s1
# v assembleru GNU AS pro architekturu AArch64.
#
# Autor: Pavel Tišnovský
 
 
 
# Linux kernel system call table
sys_exit=93
 
# List of syscalls for AArch64:
# https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h
 
 
 
#-----------------------------------------------------------------------------
.section .data
 
 
 
#-----------------------------------------------------------------------------
.section .bss
 
 
 
#-----------------------------------------------------------------------------
.section .text
        .global _start          // tento symbol má být dostupný i z linkeru
 
_start:
        fmov s1, #1.00          // načtení konstanty do registru
 
        mov  x8, #sys_exit      // číslo sycallu pro funkci "exit"
        mov  x0, #0             // exit code = 0
        svc  0                  // volání Linuxového kernelu

Obsah výsledného binárního souboru po překladu a slinkování je nyní poněkud odlišný:

$ objdump -d a.out
 
a.out:     file format elf64-littleaarch64
 
 
Disassembly of section .text:
 
0000000000400078 <_start>:
  400078:       1e2e1001        fmov    s1, #1.000000000000000000e+00
  40007c:       d2800ba8        mov     x8, #0x5d                       // #93
  400080:       d2800000        mov     x0, #0x0                        // #0
  400084:       d4000001        svc     #0x0

8. Nepodporované konstanty aneb (logické) omezení možností RISCových instrukcí

Některé konstanty (přesněji řečeno jejich naprostou většinu) není možné uložit do instrukčního slova instrukce FMOV, o čemž se můžeme velmi snadno přesvědčit:

.section .text
        .global _start          // tento symbol má být dostupný i z linkeru
 
_start:
        fmov s1, #0.00          // načtení konstanty do registru
 
        mov  x8, #sys_exit      // číslo sycallu pro funkci "exit"
        mov  x0, #0             // exit code = 0
        svc  0                  // volání Linuxového kernelu

Při pokusu o překlad tohoto kódu nastane chyba:

fmov3.s: Assembler messages:
fmov3.s:33: Error: invalid floating-point constant at operand 2 -- `fmov s1,#0.00'

9. Vynulování registrů Dx a Sx

V šesté kapitole jsme si řekli, že vynulování registrů Dx či Sx dosáhneme přesunem nulové hodnoty z registru XZR resp. WZR. Nejdříve si ukažme vynulování registru S1, tedy typu single:

# asmsyntax=as
 
# Načtení FP konstanty do registru s1
# v assembleru GNU AS pro architekturu AArch64.
#
# Autor: Pavel Tišnovský
 
 
 
# Linux kernel system call table
sys_exit=93
 
# List of syscalls for AArch64:
# https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h
 
 
 
#-----------------------------------------------------------------------------
.section .data
 
 
 
#-----------------------------------------------------------------------------
.section .bss
 
 
 
#-----------------------------------------------------------------------------
.section .text
        .global _start          // tento symbol má být dostupný i z linkeru
 
_start:
        fmov s1, wzr            // načtení konstanty do registru
 
        mov  x8, #sys_exit      // číslo sycallu pro funkci "exit"
        mov  x0, #0             // exit code = 0
        svc  0                  // volání Linuxového kernelu

Způsob překladu:

$ objdump -d a.out
 
a.out:     file format elf64-littleaarch64
 
 
Disassembly of section .text:
 
0000000000400078 <_start>:
  400078:       1e2703e1        fmov    s1, wzr
  40007c:       d2800ba8        mov     x8, #0x5d                       // #93
  400080:       d2800000        mov     x0, #0x0                        // #0
  400084:       d4000001        svc     #0x0

A vynulování registru D1, tedy typu double:

# asmsyntax=as
 
# Načtení FP konstanty do registru d1
# v assembleru GNU AS pro architekturu AArch64.
#
# Autor: Pavel Tišnovský
 
 
 
# Linux kernel system call table
sys_exit=93
 
# List of syscalls for AArch64:
# https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h
 
 
 
#-----------------------------------------------------------------------------
.section .data
 
 
 
#-----------------------------------------------------------------------------
.section .bss
 
 
 
#-----------------------------------------------------------------------------
.section .text
        .global _start          // tento symbol má být dostupný i z linkeru
 
_start:
        fmov d1, xzr            // načtení konstanty do registru
 
        mov  x8, #sys_exit      // číslo sycallu pro funkci "exit"
        mov  x0, #0             // exit code = 0
        svc  0                  // volání Linuxového kernelu

A způsob překladu do objektového kódu:

$ objdump -d a.out
 
a.out:     file format elf64-littleaarch64
 
 
Disassembly of section .text:
 
0000000000400078 <_start>:
  400078:       9e6703e1        fmov    d1, xzr
  40007c:       d2800ba8        mov     x8, #0x5d                       // #93
  400080:       d2800000        mov     x0, #0x0                        // #0
  400084:       d4000001        svc     #0x0

10. Nepřímé načtení konstant s libovolnou hodnotou do registrů DxSx

Konstanty, které nelze zakódovat do instrukčního slova instrukce FMOV, se většinou celé (32 bitů či 64 bitů) ukládají do paměti a načítají instrukcí LDR. Jediný problém spočívá v tom, že assembler (resp. GNU Assembler) nedokáže rozpoznat konstanty typu single či double, takže je nutné hodnotu nejdříve získat konverzí do decimálního či hexadecimálního tvaru. K tomuto účelu lze použít aplikaci dostupnou na adrese https://baseconvert.com/ieee-754-floating-point.

Konkrétně může načtení 64bitové konstanty do registru D1 vypadat takto:

ldr  d1, =0x3FF0000000000000 // načtení konstanty do registru

Následuje příklad použití:

# asmsyntax=as
 
# Načtení FP konstanty do registru d1
# v assembleru GNU AS pro architekturu AArch64.
#
# Autor: Pavel Tišnovský
 
 
 
# Linux kernel system call table
sys_exit=93
 
# List of syscalls for AArch64:
# https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h
 
 
 
#-----------------------------------------------------------------------------
.section .data
 
 
 
#-----------------------------------------------------------------------------
.section .bss
 
 
 
#-----------------------------------------------------------------------------
.section .text
        .global _start          // tento symbol má být dostupný i z linkeru
 
_start:
        ldr  d1, =0x3FF0000000000000 // načtení konstanty do registru
 
 
        mov  x8, #sys_exit      // číslo sycallu pro funkci "exit"
        mov  x0, #0             // exit code = 0
        svc  0                  // volání Linuxového kernelu

Způsob překladu do strojového kódu je v tomto případě velmi zajímavý, protože samotná konstanta je uložena za samotným kódem a je načtena s využitím „krátké“ adresy uložené přímo v instrukčním slovu instrukce ldr:

$ objdump -d a.out
 
a.out:     file format elf64-littleaarch64
 
 
Disassembly of section .text:
 
0000000000400078 <_start>:
  400078:       5c000081        ldr     d1, 400088 <_start+0x10>
  40007c:       d2800ba8        mov     x8, #0x5d                       // #93
  400080:       d2800000        mov     x0, #0x0                        // #0
  400084:       d4000001        svc     #0x0
  400088:       4048f5c3        .word   0x4048f5c3
  40008c:       00000000        .word   0x00000000

11. Přenos operandů mezi registry

Další skupina instrukcí mikroprocesorů s architekturou AArch64 sice taktéž používá mnemotechnickou zkratku FMOV, ovšem neslouží k načtení konstanty, ale k přenosu operandu (tedy konkrétní hodnoty) mezi různými registry. Zajímavé je, že je možné přenášet operandy mezi celočíselnými registry a FP registry; v takovém případě se přenese přesný bitový obraz uloženého čísla a neprovádí se žádné konverze (zaokrouhlení atd.). Poslední dvě instrukce jsou užitečné pro přenos 64 bitů do nebo naopak ze 128 bitového registru Vd:

# Instrukce Stručný popis
1 FMOV Sd, Sn přenos mezi registry (oba typu single)
2 FMOV Wd, Sn přenos mezi registry (32bitový integer, single)
3 FMOV Sd, Wn přenos mezi registry (32bitový integer, single)
     
4 FMOV Dd, Dn přenos mezi registry (oba typu double)
5 FMOV Xd, Dn přenos mezi registry (64bitový integer, double)
6 FMOV Dd, Xn přenos mezi registry (64bitový integer, double)
     
7 FMOV Xd, Vn.D[1] přenos 64 bitů Vn<127:64> → Xd
8 FMOV Vd.D[1], Xn přenos 64 bitů Xn → Vd<127:64>, ostatní bity Vd se nezmění
Poznámka: povšimněte si, že až na 64bitové platformě konečně došlo k unifikaci mezi celočíselnými registry a FP registry s ohledem na bitovou šířku operandů a rozlišením jednoduchá přesnost/poloviční slovo a dvojitá přesnost/celé slovo..

12. Ukázka přenosu mezi celočíselným a FP registrem

V dalším demonstračním příkladu je ukázán způsob přenosu dat mezi registry X1, D1 a X2 s využitím instrukce FMOV:

# asmsyntax=as
 
# Přesuny mezi celočíselnými a FP registry
# v assembleru GNU AS pro architekturu AArch64.
#
# Autor: Pavel Tišnovský
 
 
 
# Linux kernel system call table
sys_exit=93
 
# List of syscalls for AArch64:
# https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h
 
 
 
#-----------------------------------------------------------------------------
.section .data
 
 
 
#-----------------------------------------------------------------------------
.section .bss
 
 
 
#-----------------------------------------------------------------------------
.section .text
        .global _start          // tento symbol má být dostupný i z linkeru
 
_start:
        mov  x1, #0x1234        // načtení celočíselné konstanty
        fmov d1, x1             // přenos do FP registru
        fmov x2, d1             // zpětný přenos do celočíselného registru
 
        mov  x8, #sys_exit      // číslo sycallu pro funkci "exit"
        mov  x0, #0             // exit code = 0
        svc  0                  // volání Linuxového kernelu

Přenosy si můžeme ověřit „naživo“ v GNU Debuggeru. Pro tento účel se provede překlad s využitím přepínače -g:

$ as -g -o a.o src.s
$ ld -g -o a.out a.o

Výsledný binární soubor načteme do GNU Debuggeru:

$ gdb a.out

Nastavíme breakpoint na začátek kódu, tedy na návěští _start:

(gdb) b _start

Následně program spustíme:

(gdb) r

Program se zastaví na první instrukci (díky breakpointu), takže si zobrazíme obsah pracovních registrů:

(gdb) info registers
x0             0x0                 0
x1             0x0                 0
x2             0x0                 0
...
...
...

Další instrukce se provede příkazem n (next). Opět si zobrazíme obsah registrů:

(gdb) info registers
x0             0x0                 0
x1             0x1234              4660
x2             0x0                 0
...
...
...

Další instrukce provádí přenos do D1, takže si musíme zobrazit obsah registrů matematického koprocesoru:

(gdb) info float
d0             {f = 0x0, u = 0x0, s = 0x0} {f = 0, u = 0, s = 0}
d1             {f = 0x0, u = 0x1234, s = 0x1234} {f = 2.3023459096202089e-320, u = 4660, s = 4660}
d2             {f = 0x0, u = 0x0, s = 0x0} {f = 0, u = 0, s = 0}
...
...
...

A poslední instrukce přenese stejná data do celočíselného registru X2:

(gdb) info registers
x0             0x0                 0
x1             0x1234              4660
x2             0x1234              4660
x3             0x0                 0
...
...
...

13. Konverze mezi různými formáty

Pro konverzi hodnot mezi různými numerickými formáty s plovoucí řádovou čárkou (half float, single, double) slouží instrukce nazvaná FCVT (neboli float convert). Některé převody lze provést bez problémů (neztratí se tedy ani přesnost ani rozsah), u dalších převodů buď ztratíme přesnost nebo bude hodnota převedena na ∞ nebo -∞ (což je ovšem očekávané chování):

# Instrukce Stručný popis
1 FCVT Sd, Hn převod mezi formátem half float a single (bez ztráty)
2 FCVT Hd, Sn převod mezi formátem single a half float (ztráta přesnosti a/nebo rozsahu)
3 FCVT Dd, Hn převod mezi formátem half float a double (bez ztráty)
4 FCVT Hd, Dn převod mezi formátem double a half float (ztráta přesnosti a/nebo rozsahu)
5 FCVT Dd, Sn převod mezi formátem single a double (bez ztráty)
6 FCVT Sd, Dn převod mezi formátem double a single (ztráta přesnosti a/nebo rozsahu)

Opět se podívejme na prozatím velmi jednoduchý příklad použití při konverzi mezi hodnotami lokálních proměnných:

float  x = 1.0;
double y = 1.0;
double z = x;
float  w = y;

Překlad tohoto úryvku kódu do assembleru:

// float  x = 1.0;
fmov    s0, 1.0e+0
str     s0, [sp, 28]
 
// double y = 1.0;
fmov    d0, 1.0e+0
str     d0, [sp, 16]
 
// double z = x;
ldr     s0, [sp, 28]
fcvt    d0, s0
str     d0, [sp, 8]
 
// float  w = y;
ldr     d0, [sp, 16]
fcvt    s0, d0
str     s0, [sp, 4]

14. Převod FP hodnot na celá čísla (zaokrouhlení)

Poměrně rozsáhlá skupina strojových instrukcí slouží pro převod FP hodnot (tedy numerických hodnot reprezentovaných v systému plovoucí řádové čárky) na celá čísla. Podívejme se na tabulku se seznamem těchto instrukcí:

# Instrukce Stručný popis
1 FCVTAS konverze FP na typ signed integer (tedy se znaménkem), zaokrouhlení směrem k nekonečnům
2 FCVTAU dtto, ale konverze na datový typ unsigned integer
3 FCVTMS konverze FP hodnoty na signed integer se zaokrouhlením směrem k -∞
4 FCVTMU konverze FP hodnoty na unsigned integer se zaokrouhlením směrem k -∞
5 FCVTNS konverze FP hodnoty se zaokrouhlením na nejbližší sudé číslo
6 FCVTNU dtto, ovšem nyní pro unsigned integer
7 FCVTPS konverze FP hodnoty na signed integer se zaokrouhlením směrem k +∞
8 FCVTPU konverze FP hodnoty na unsigned integer se zaokrouhlením směrem k +∞
9 FCVTZS konverze FP hodnoty na signed integer se zaokrouhlením směrem k nule
10 FCVTZU konverze na unsigned integer se zaokrouhlením směrem k nule
11 SCVTF zpětná konverze na FP hodnotu (desetinná část bude pochopitelně nulová)
12 UCVTF zpětná konverze na FP hodnotu (desetinná část bude pochopitelně nulová)

Instrukce FCVTNS a FCVTNU zaokrouhlují na nejbližší sudé číslo ty hodnoty, které leží přesně v polovině intervalu (1/2).

Nezapomeneme si samozřejmě ukázat, jak tyto instrukce používá překladač v praxi:

float  x = 1.0;
double y = 2.0;
int    i = x;
int    j = y;

Způsob překladu tohoto programového bloku do assembleru vypadá následovně:

// float  x = 1.0;
fmov    s0, 1.0e+0
str     s0, [sp, 28]
 
// double y = 2.0;
fmov    d0, 2.0e+0
str     d0, [sp, 16]
 
// int    i = x;
ldr     s0, [sp, 28]
fcvtzs  w0, s0
str     w0, [sp, 12]
 
// int    j = y;
ldr     d0, [sp, 16]
fcvtzs  w0, d0
str     w0, [sp, 8]

15. Základní aritmetické operace s hodnotami s plovoucí řádovou čárkou

Poměrně rozsáhlá je skupina instrukcí určených pro provádění základních aritmetických operací, k nimž navíc přidáváme instrukce pro výpočet absolutní hodnoty, odmocniny, minima, maxima atd.:

# Instrukce Stručný popis
1 FABS výpočet absolutní hodnoty (jeden zdrojový operand)
2 FNEG negace hodnoty (jeden zdrojový operand)
3 FSQRT výpočet druhé odmocniny (jeden zdrojový operand)
     
4 FADD součet
5 FSUB rozdíl
6 FMUL součin
7 FNMUL součin a následná změna znaménka výsledku
8 FDIV podíl
     
9 FMIN výpočet minima, pokud je jeden ze zdrojových operandů NaN, vrací NaN
10 FMAX výpočet maxima, pokud je jeden ze zdrojových operandů NaN, vrací NaN
11 FMINNUM výpočet minima, pokud je jeden ze zdrojových operandů NaN, vrací druhý operand
12 FMAXNUM výpočet maxima, pokud je jeden ze zdrojových operandů NaN, vrací druhý operand
     
13 FMADD (MAC) cíl = zdroj1 + zdroj2 × zdroj3
14 FMSUB cíl = zdroj1 – zdroj2 × zdroj3
15 FNMADD cíl = -zdroj1 + zdroj2 × zdroj3
16 FNMSUB cíl = -zdroj1 – zdroj2 × zdroj3
Poznámka: stále se ovšem jedná o koncept RISC, pouze došlo k určitému posunu ve významu slova „Reduced“ v této zkratce.

Opět se podívejme na příklad, tentokrát s nepatrně složitějším výpočtem:

float  x = 1.0;
float  y = 2.0;
float  z = 3.0;
float  w = x*y + y/z + fabs(z);

Tento příklad se (bez optimalizací) přeloží následovně:

// float  x = 1.0;
fmov    s0, 1.0e+0
str     s0, [sp, 12]
 
// float  y = 2.0;
fmov    s0, 2.0e+0
str     s0, [sp, 8]
 
// float  w = x*y + y/z + fabs(z);
fmov    s0, 3.0e+0
str     s0, [sp, 4]
 
// float  w = x*y + y/z + fabs(z);
ldr     s1, [sp, 12]
ldr     s0, [sp, 8]
fmul    s1, s1, s0 // x*y
ldr     s2, [sp, 8]
ldr     s0, [sp, 4]
fdiv    s0, s2, s0 // y/z
fadd    s1, s1, s0
ldr     s0, [sp, 4]
fabs    s0, s0
fadd    s0, s1, s0

Kombinace aritmetické operace s konverzí výsledku:

float  x = 1.0;
double y = 1.0;
float  z = x+y;

Se může přeložit takto:

// float  x = 1.0;
fmov    s0, 1.0e+0
str     s0, [sp, 28]
 
// double y = 1.0;
fmov    d0, 1.0e+0
str     d0, [sp, 16]
 
// float  z = x+y;
ldr     s0, [sp, 28]
fcvt    d1, s0
ldr     d0, [sp, 16]
fadd    d0, d1, d0
fcvt    s0, d0
str     s0, [sp, 12]
Poznámka: demonstrační příklad bude uveden příště.

16. Porovnání operandů s hodnotami s plovoucí řádovou čárkou

Instrukce, které slouží pro porovnání obsahu dvou FP registrů, nastavují příznakové bity N, V, Z a C (prakticky stejným způsobem, jako instrukce celočíselné). To znamená, že tyto instrukce je možné přímo zkombinovat například s podmíněnými skoky:

# Instrukce Stručný popis
1 FCMP porovnání dvou FP operandů na rovnost, popř. porovnání s nulou
2 FCMPE dtto, ovšem pokud je jeden z operandů NaN, dojde k výjimce
3 FCCMP pokud je podmínka splněna, provede se porovnání, jinak se příznakové bity nastaví na určenou konstantu
4 FCCMPE dtto ale s kontrolou operandů na NaN
     
5 FCSEL obdoba CSEL, ovšem pro FP operandy (čtvrtým parametrem je podmínka)

Podívejme se nyní na jednoduchý demonstrační příklad, opět využívající lokální proměnné uložené na zásobníkovém rámci:

float  x = 1.0;
float  y = 10.0;
float  z = 20.0;
int i = x == y;
int j = x < y;
int k = x <= y;
int l = x != y;
int m = x > y;

Způsob překladu neoptimalizujícím překladačem:

float  x = 1.0;
fmov    s0, 1.0e+0
str     s0, [sp, 28]
 
// float  y = 10.0;
fmov    s0, 1.0e+1
str     s0, [sp, 24]
 
// float  z = 20.0;
fmov    s0, 2.0e+1
str     s0, [sp, 20]
 
// int i = x == y;
ldr     s1, [sp, 28]
ldr     s0, [sp, 24]
fcmp    s1, s0
cset    w0, eq         // testuje se příznakový bit Z (zero)
uxtb    w0, w0         // rozšíření osmibitové hodnoty na 32 bitů
str     w0, [sp, 16]
 
// int j = x < y;
ldr     s1, [sp, 28]
ldr     s0, [sp, 24]
fcmpe   s1, s0
cset    w0, mi
uxtb    w0, w0         // rozšíření osmibitové hodnoty na 32 bitů
str     w0, [sp, 12]
 
// int k = x <= y;
ldr     s1, [sp, 28]
ldr     s0, [sp, 24]
fcmpe   s1, s0
cset    w0, ls
uxtb    w0, w0         // rozšíření osmibitové hodnoty na 32 bitů
str     w0, [sp, 8]
 
// int l = x != y;
ldr     s1, [sp, 28]
ldr     s0, [sp, 24]
fcmp    s1, s0
cset    w0, ne         // testuje se příznakový bit Z (zero)
uxtb    w0, w0         // rozšíření osmibitové hodnoty na 32 bitů
str     w0, [sp, 4]
 
// int m = x > y;
ldr     s1, [sp, 28]
ldr     s0, [sp, 24]
fcmpe   s1, s0
cset    w0, gt
uxtb    w0, w0         // rozšíření osmibitové hodnoty na 32 bitů
Poznámka: demonstrační příklad bude opět uveden až příště.

17. SIMD operace

Zbývá nám si popsat „maličkost“ a to konkrétně SIMD operace, které umožňují provádět výpočty nad celými vektory hodnot. Ve skutečnosti je počet „vektorových“ instrukcí větší, než počet všech zbývajících instrukcí (včetně instrukcí matematického koprocesoru), takže si na jejich popis vyhradíme celý článek.

bitcoin_skoleni

Poznámka: použití těchto instrukcí má velký vliv na celkovou výkonnost aplikace, což na druhou stranu představuje problém, protože záleží jak na překladači, tak i na programátorovi (intrinsic), jakým způsobem a jak efektivně dokáže této vlastnosti mikroprocesorů AArch64 využít.

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

Všechny minule i dnes popisované demonstrační příklady byly společně s podpůrným souborem Makefile určeným pro jejich překlad či naopak pro disassembling, uloženy do GIT repositáře dostupného na adrese https://github.com/tisnik/pre­sentations/. Všechny příklady jsou určeny pro standardní GNU Assembler a používají výchozí syntaxi procesorů AArch64. Následuje tabulka s odkazy na zdrojové kódy příkladů i na již zmíněné podpůrné skripty:

# Příklad Popis Zdrojový kód
1 template.s šablona pro programy psané v assembleru https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/template.s
2 template2.s šablona pro programy psané v assembleru, založeno na makrech https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/template.s
3 hello_world1.s základní podoba programu typu „Hello, world!“ https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/hello_wor­ld1.s
4 hello_world2.s přepis tištěného řetězce https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/hello_wor­ld2.s
5 hello_world3.s refaktoring, použití maker https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/hello_wor­ld3.s
6 aritmetic1.s základní aritmetické operace https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/aritmetic1­.s
7 infinite_loop.s nekonečná programová smyčka realizovaná instrukcí b https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/infinite_lo­op.s
8 loop1-aarch64-v1.s základní varianta počítané programové smyčky https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/loop1-aarch64-v1.s
9 loop1-aarch64-v2.s optimalizace – odstranění instrukce CMP https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/loop1-aarch64-v2.s
10 loop1-aarch64-v3.s optimalizace – použití instrukce CBNZ https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/loop1-aarch64-v3.s
11 loop1-aarch64-v4.s automatické zvýšení adresy (ukazatele) https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/loop1-aarch64-v4.s
12 move1.s přesun bloku dat https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/move1.s
13 move2.s přepis s využitím maker https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/move2.s
14 move3.s problém s vícenásobným použitím makra https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/move3.s
15 move4.s vyřešení předchozího problému https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/move4.s
16 move5.s jednoduchý benchmark – přesun bloků po bajtech https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/move5.s
17 move6.s jednoduchý benchmark – přesun bloků po slovech https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/move6.s
18 move7.s jednoduchý benchmark – přesun bloků po dvojicích slov https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/move7.s
       
19 fmov1.s načtení konstanty typu double do registru d1 https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/fmov1.s
20 fmov2.s načtení konstanty typu single do registru s1 https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/fmov2.s
21 fmov3.s použití nekorektní konstanty https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/fmov3.s
22 fmov4.s načtení nuly do registru s1 https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/fmov4.s
23 fmov5.s načtení nuly do registru d1 https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/fmov5.s
24 fmov6.s nepřímé načtení konstanty instrukcí ldr https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/fmov6.s
25 fmov7.s přesuny mezi celočíselnými a FP registry https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/fmov7.s
       
26 Makefile soubor pro překlad všech příkladů nástrojem make https://github.com/tisnik/pre­sentations/blob/master/as­sembler/aarch64/Makefile

19. Předchozí články o architektuře AArch64

S architekturou AArch64 jsme se již na stránkách Roota setkali, a to konkrétně v následujících článcích, z nichž v dnešním článku vycházíme:

  1. 64bitové mikroprocesory s architekturou AArch64
    https://www.root.cz/clanky/64bitove-mikroprocesory-s-architekturou-aarch64/
  2. Instrukční sada AArch64
    https://www.root.cz/clanky/instrukcni-sada-aarch64/
  3. Instrukční sada AArch64 (2.část)
    https://www.root.cz/clanky/instrukcni-sada-aarch64–2-cast/
  4. Tvorba a ladění programů v assembleru mikroprocesorů AArch64
    https://www.root.cz/clanky/tvorba-a-ladeni-programu-v-assembleru-mikroprocesoru-aarch64/
  5. Instrukční sada AArch64: technologie NEON
    https://www.root.cz/clanky/instrukcni-sada-aarch64-technologie-neon/
  6. Specifické vlastnosti procesorů AArch64: základní instrukce
    https://www.root.cz/clanky/specificke-vlastnosti-procesoru-aarch64-zakladni-instrukce/
  7. Specifické vlastnosti procesorů AArch64: podmíněné a nepodmíněné skoky, adresování dat
    https://www.root.cz/clanky/specificke-vlastnosti-procesoru-aarch64-podminene-a-nepodminene-skoky-adresovani-dat/
  8. Specifické vlastnosti procesorů AArch64: přenos bloků dat a instrukce s podmínkou
    https://www.root.cz/clanky/specificke-vlastnosti-procesoru-aarch64-prenos-bloku-dat-a-instrukce-s-podminkou/

20. Odkazy na Internetu

  1. Arm Architecture Reference Manual for A-profile architecture
    https://developer.arm.com/do­cumentation/ddi0487/latest
  2. The GNU Assembler – macros
    http://tigcc.ticalc.org/doc/gnu­asm.html#SEC109
  3. GNU Binutils
    https://sourceware.org/binutils/
  4. Documentation for binutils 2.38
    https://sourceware.org/binutils/docs-2.38/
  5. AArch64 Instruction Set Architecture
    https://developer.arm.com/ar­chitectures/learn-the-architecture/aarch64-instruction-set-architecture/instruction-sets-in-the-arm-architecture
  6. Arm Armv8-A A32/T32 Instruction Set Architecture
    https://developer.arm.com/do­cumentation/ddi0597/2021–12/?lang=en
  7. Comparison of ARMv8-A cores
    https://en.wikipedia.org/wi­ki/Comparison_of_ARMv8-A_cores
  8. Cortex-A32 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a32-processor.php
  9. Cortex-A35 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a35-processor.php
  10. Cortex-A53 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a53-processor.php
  11. Cortex-A57 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a57-processor.php
  12. Cortex-A72 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a72-processor.php
  13. Cortex-A73 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a73-processor.php
  14. Apple A7 (SoC založen na CPU Cyclone)
    https://en.wikipedia.org/wi­ki/Apple_A7
  15. System cally pro AArch64 na Linuxu
    https://github.com/torval­ds/linux/blob/master/inclu­de/uapi/asm-generic/unistd.h
  16. Architectures/AArch64 (FedoraProject.org)
    https://fedoraproject.org/wi­ki/Architectures/AArch64
  17. SIG pro AArch64 (CentOS)
    https://wiki.centos.org/Spe­cialInterestGroup/AltArch/A­Arch64
  18. The ARMv8 instruction sets
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.den0024a/ch05s01.html
  19. A64 Instruction Set
    https://developer.arm.com/pro­ducts/architecture/instruc­tion-sets/a64-instruction-set
  20. Switching between the instruction sets
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.den0024a/ch05s01.html
  21. The A64 instruction set
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.den0024a/ch05s01.html
  22. Introduction to ARMv8 64-bit Architecture
    https://quequero.org/2014/04/in­troduction-to-arm-architecture/
  23. MCU market turns to 32-bits and ARM
    http://www.eetimes.com/do­cument.asp?doc_id=1280803
  24. Cortex-M0 Processor (ARM Holdings)
    http://www.arm.com/produc­ts/processors/cortex-m/cortex-m0.php
  25. Cortex-M0+ Processor (ARM Holdings)
    http://www.arm.com/produc­ts/processors/cortex-m/cortex-m0plus.php
  26. ARM Processors in a Mixed Signal World
    http://www.eeweb.com/blog/arm/arm-processors-in-a-mixed-signal-world
  27. ARM Architecture (Wikipedia)
    https://en.wikipedia.org/wi­ki/ARM_architecture
  28. DSP for Cortex-M
    https://developer.arm.com/techno­logies/dsp/dsp-for-cortex-m
  29. Cortex-M processors in DSP applications? Why not?!
    https://community.arm.com/pro­cessors/b/blog/posts/cortex-m-processors-in-dsp-applications-why-not
  30. White Paper – DSP capabilities of Cortex-M4 and Cortex-M7
    https://community.arm.com/pro­cessors/b/blog/posts/white-paper-dsp-capabilities-of-cortex-m4-and-cortex-m7
  31. Q (number format)
    https://en.wikipedia.org/wi­ki/Q_%28number_format%29
  32. TriCore Architecture & Core
    http://www.infineon.com/cms/en/pro­duct/microcontroller/32-bit-tricore-tm-microcontroller/tricore-tm-architecture-and-core/channel.html?channel=ff80808112ab681d0112­ab6b73d40837
  33. TriCoreTM V1.6 Instruction Set: 32-bit Unified Processor Core
    http://www.infineon.com/dgdl/tc_v131_in­structionset_v138.pdf?file­Id=db3a304412b407950112b409b6dd0352
  34. TriCore v2.2 C Compiler, Assembler, Linker Reference Manual
    http://tasking.com/suppor­t/tricore/tc_reference_gu­ide_v2.2.pdf
  35. Infineon TriCore (Wikipedia)
    https://en.wikipedia.org/wi­ki/Infineon_TriCore
  36. C166®S V2 Architecture & Core
    http://www.infineon.com/cms/en/pro­duct/microcontroller/16-bit-c166-microcontroller/c166-s-v2-architecture-and-core/channel.html?channel=db3a304312bef5660112­c3011c7d01ae
  37. Comparing four 32-bit soft processor cores
    http://www.eetimes.com/au­thor.asp?section_id=14&doc_id=1286116
  38. RISC-V Instruction Set
    http://riscv.org/download­.html#spec_compressed_isa
  39. RISC-V Spike (ISA Simulator)
    http://riscv.org/download.html#isa-sim
  40. RISC-V (Wikipedia)
    https://en.wikipedia.org/wiki/RISC-V
  41. David Patterson (Wikipedia)
    https://en.wikipedia.org/wi­ki/David_Patterson_(compu­ter_scientist)
  42. OpenRISC (oficiální stránky projektu)
    http://openrisc.io/
  43. OpenRISC architecture
    http://openrisc.io/architecture.html
  44. Emulátor OpenRISC CPU v JavaScriptu
    http://s-macke.github.io/jor1k/demos/main.html
  45. OpenRISC (Wikipedia)
    https://en.wikipedia.org/wi­ki/OpenRISC
  46. OpenRISC – instrukce
    http://sourceware.org/cgen/gen-doc/openrisc-insn.html
  47. OpenRISC – slajdy z přednášky o projektu
    https://iis.ee.ethz.ch/~gmichi/a­socd/lecturenotes/Lecture6­.pdf
  48. Berkeley RISC
    http://en.wikipedia.org/wi­ki/Berkeley_RISC
  49. Great moments in microprocessor history
    http://www.ibm.com/develo­perworks/library/pa-microhist.html
  50. Microprogram-Based Processors
    http://research.microsoft.com/en-us/um/people/gbell/Computer_Struc­tures_Principles_and_Exam­ples/csp0167.htm
  51. Great Microprocessors of the Past and Present
    http://www.cpushack.com/CPU/cpu1.html
  52. A Brief History of Microprogramming
    http://www.cs.clemson.edu/~mar­k/uprog.html
  53. What is RISC?
    http://www-cs-faculty.stanford.edu/~ero­berts/courses/soco/projec­ts/2000–01/risc/whatis/
  54. RISC vs. CISC
    http://www-cs-faculty.stanford.edu/~ero­berts/courses/soco/projec­ts/2000–01/risc/risccisc/
  55. RISC and CISC definitions:
    http://www.cpushack.com/CPU/cpu­AppendA.html
  56. FPGA
    https://cs.wikipedia.org/wi­ki/Programovateln%C3%A9_hra­dlov%C3%A9_pole
  57. The Evolution of RISC
    http://www.ibm.com/develo­perworks/library/pa-microhist.html#sidebar1
  58. disasm.pro
    https://disasm.pro/
  59. Exploring AArch64 assembler – Chapter 5
    https://thinkingeek.com/2016/11/13/ex­ploring-aarch64-assembler-chapter-5/
  60. Brain Floating Point – nový formát uložení čísel pro strojové učení a chytrá čidla
    https://www.root.cz/clanky/brain-floating-point-ndash-novy-format-ulozeni-cisel-pro-strojove-uceni-a-chytra-cidla/
  61. Why Intel is betting on BFLOAT16 to be a game changer for deep learning training? Hint: Range trumps Precision
    https://hub.packtpub.com/why-intel-is-betting-on-bfloat16-to-be-a-game-changer-for-deep-learning-training-hint-range-trumps-precision/
  62. half-rs (pro Rust)
    https://github.com/starkat99/half-rs
  63. float16 (pro Go)
    https://github.com/x448/float16
  64. bfloat16 – Hardware Numerics Definition
    https://software.intel.com/en-us/download/bfloat16-hardware-numerics-definition
  65. Intel Prepares To Graft Google’s Bfloat16 Onto Processors
    https://www.nextplatform.com/2019/07/15/in­tel-prepares-to-graft-googles-bfloat16-onto-processors/
  66. Norma IEEE 754 a příbuzní: formáty plovoucí řádové tečky
    https://www.root.cz/clanky/norma-ieee-754-a-pribuzni-formaty-plovouci-radove-tecky/
  67. IEEE-754 Floating-Point Conversion
    http://babbage.cs.qc.cuny.edu/IEEE-754.old/32bit.html
  68. Small Float Formats
    https://www.khronos.org/o­pengl/wiki/Small_Float_For­mats
  69. Binary-coded decimal
    https://en.wikipedia.org/wiki/Binary-coded_decimal
  70. Chen–Ho encoding
    https://en.wikipedia.org/wi­ki/Chen%E2%80%93Ho_encoding
  71. Densely packed decimal
    https://en.wikipedia.org/wi­ki/Densely_packed_decimal
  72. A Summary of Chen-Ho Decimal Data encoding
    http://speleotrove.com/decimal/chen-ho.html
  73. Art of Assembly language programming: The 80×87 Floating Point Coprocessors
    https://courses.engr.illi­nois.edu/ece390/books/arto­fasm/CH14/CH14–3.html
  74. Art of Assembly language programming: The FPU Instruction Set
    https://courses.engr.illi­nois.edu/ece390/books/arto­fasm/CH14/CH14–4.html
  75. INTEL 80387 PROGRAMMER'S REFERENCE MANUAL
    http://www.ragestorm.net/dow­nloads/387intel.txt
  76. Floating-Point Formats
    http://www.quadibloc.com/com­p/cp0201.htm
  77. Floating Point Numbers
    https://floating-point-gui.de/formats/fp/
  78. Float exposed
    https://float.exposed/0×40490000
  79. Float Toy
    http://evanw.github.io/float-toy/
  80. IEEE-754 visualization
    https://bartaz.github.io/ieee754-visualization/
  81. IEEE-754 Floating Point Converter
    https://www.h-schmidt.net/FloatConverter/I­EEE754.html
  82. IEEE 754 Calculator
    http://weitz.de/ieee/
  83. IEEE-754 Float Calculator
    https://mason.cc/float/

Autor článku

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