Zpracování hodnot typu half float (fp16) na platformě AArch64

9. 9. 2025
Doba čtení: 37 minut

Sdílet

Procesor ARM Cortex A57 A53
Autor: Open Grid Scheduler / Grid Engine
Programátoři, zabývající se generativní AI či zpracováním přirozeného jazyka, se často setkávají s vektory obsahujícími prvky typu half float (FP16). Vysvětlíme si, jak se s těmito hodnotami pracuje na platformě AArch64.

Obsah

1. Zpracování hodnot typu half float (fp16) na platformě AArch64

2. Podpora technologie NEON v ARMovských jádrech AArch64

3. Pracovní registry používané instrukcemi NEON

4. Podporované formáty prvků vektorů

5. Vrácení hodnoty prvního resp. druhého parametru typu half float

6. Překlad operace součtu dvou hodnot typu half float

7. Překlad využívající instrukce pro přímé operace s hodnotami typu half float

8. Způsob překladu všech čtyř základních aritmetických operací nad hodnotami typu half float

9. Překlad operací pro porovnání dvou hodnot typu half float

10. NEON a operace nad celými vektory

11. Přičtení konstanty ke všem prvkům pole

12. Součet odpovídajících si prvků polí typu half float

13. Pomáháme překladači: klíčové slovo restrict

14. Překlad součtu polí s menším počtem prvků

15. Intrinsic pro provádění operací s hodnotami typu half float

16. Realizace součtu dvou hodnot typu half float přes intrinsic funkci

17. Základní aritmetické operace s hodnotami typu half float realizované přes intrinsic

18. Porovnání operandů typu half float realizované přes intrinsic

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

20. Odkazy na Internetu

1. Zpracování hodnot typu half float (fp16) na platformě AArch64

Jak již bylo napsáno v perexu dnešního článku, mohou se programátoři, kteří se zabývají vývojem v oblasti generativní AI, zpracování přirozeného jazyka, embeddingu a embedding modelů atd., setkat s nutností zpracování skalárních hodnot či celých vektorů s prvky typu half float (FP16). Důvody jsou zřejmé: pro mnoho účelů rozsah i přesnost hodnot uložených jako half float plně vyhovuje požadavkům řešeného algoritmu a oproti hodnotám single jsou paměťové požadavky poloviční (a v praxi je velký finanční rozdíl v tom, jestli servery vyžadují například 16MB RAM nebo 32MB RAM). V tomto článku si vysvětlíme, jak se s těmito hodnotami pracuje na platformě AArch64, s níž se dnes velmi často setkáme (a to právě i v oblasti GENAI). Zajímavé je, že pro zpracování skalárů i vektorů typu half float je platforma AArch64 vybavena lépe, než klasická x86–64.

V úvodní kapitole si připomeneme základní vlastnosti numerického datového typu half float.

Formát half float, jenž je dnes standardizován v IEEE 754–2008, používá pro ukládání hodnot s plovoucí řádovou čárkou pouhých šestnáct bitů, tj. dva byty. Maximální hodnota je rovna 65504, minimální hodnota (větší než nula) přibližně 5,9×10-8. Předností tohoto formátu je malá bitová šířka (umožňuje paralelní přenos po interních sběrnicích GPU) a také větší rychlost zpracování základních operací, protože pro tak malou bitovou šířku mantisy je možné některé operace „zadrátovat“ a nepočítat pomocí ALU. Také některé iterativní výpočty (sin, cos, sqrt) mohou být provedeny rychleji, než v případě plnohodnotných typů float a single.

Celkový počet bitů (bytů): 16 (2)
Bitů pro znaménko: 1
Bitů pro exponent: 5
Bitů pro mantisu: 10
BIAS (offset exponentu): 15
Přesnost: 3–4 číslice
Maximální hodnota: 65504
Minimální hodnota: –65504
Nejmenší kladná nenulová hodnota: 5,96×10-8
Nejmenší kladná normalizovaná hodnota: 6,104×10-5
Podpora záporné nuly: ano
Podpora +∞: ano
Podpora -∞: ano
Podpora NaN: ano

Typ half float je v současnosti mikroprocesory ARM do značné míry podporován, přičemž jsou povoleny i některé vektorové operace, tj. například součet dvou vektorů atd. Ty jsou realizovány technologií NEON.

Pokud se podíváme na historii mikroprocesorů ARM, zjistíme, že cesta k technologii NEON na jádrech AArch64 vlastně nebyla vůbec přímočará. První implementace „vektorových“ operací pro procesory ARM používaly rozhraní pro koprocesory, takže se vlastně používala paralelní/doplňková instrukční sada. Konkrétně se jednalo o technologii nazvanou VFP neboli Vector Floating Point. Touto technologií, která je stále na některých ARMovských jádrech podporována, jsme se již v seriálu o architekturách počítačů zabývali.

Na tomto místě je vhodné zdůraznit, že i přesto, že se v názvu VFP používá termín „vector“, nejednalo se o implementaci skutečných SIMD operací, protože se prvky vektorů zpracovávaly postupně, tedy sekvenčně (stále se však jednalo o vylepšení, protože se ušetřilo načtení instrukce a její dekódování). Z tohoto důvodu byl „vektorový režim“ VFP poměrně rychle nahrazen novou technologií nazvanou NEON označovanou též Advanced SIMD. Dnes se s VFP můžeme na některých ARMovských jádrech setkat, další jádra pak podporují jen VFPLite, kde však každá operace trvá zhruba deset strojových cyklů!

Technologie NEON již podporovala plnohodnotné SIMD operace, při použití vektorů s nejmenšími prvky o velikosti jednoho bajtu až šestnáct operací (například součtu) paralelně. Díky tomu bylo možné implementovat například dekodér pro známý formát MP3 na mikroprocesoru s taktem pouhých 10 MHz popř. implementovat AMR kodek na podobném čipu, ovšem s hodinovým taktem 13 MHz. Při použití klasických výpočtů se skalárními hodnotami by bylo nutné použít čip s vyšší hodinovou frekvencí či naopak – tento výkonný čip by již neměl dostatek výkonu pro provádění dalších činností.

Pro ukázku: ještě mikroprocesory 486DX2 s hodinovou frekvencí 66 MHz měly s dekódováním MP3 velké problémy a zvládaly jen menší bitrate a monofonní výstup). Právě díky NEONu se začaly čipy ARM používat i v některých oblastech, které byly dříve vyhrazeny digitálním signálovým procesorům.

Mimo VFP a NEON bylo pro některá (dnes již notně stará) jádra ARM vyvinuto rozšíření pro DSP operace. Konkrétně se jednalo o jádra ARMv5TE. Nové instrukce byly využitelné například při kódování a dekódování videa, při zpracování zvukového signálu (včetně zvukové syntézy), práci s rastrovými obrazy (image processing) atd. Instrukce typu Load & Store mohly pracovat s registrovými páry, zavedeny byly nové adresovací režimy, aritmetika se saturací (tj. bez přetečení) a taktéž instrukce typu „multiply and accumulate (MAC)“ 16×16 bitů a 32×16 bitů, které mohly být v instrukční pipeline vykonány v rozmezí jednoho taktu (v jejich průběhu se tedy mohly začít zpracovávat další instrukce). Uvádí se, že při zpracování signálů byla výkonnost nových instrukcí v jádrech ARMv5TE dvakrát až třikrát vyšší, než při použití „běžných“ jader ARMv5 (samozřejmě za předpokladu ruční optimalizace kódu, což ostatně až na některé výjimky platí dodnes, protože intrinsic nejsou samospasitelné).

Další odbočkou byla technologie SIMD extensions for multimedia pro jádra ARMv6, která byla později nahrazena Advanced SIMD (NEONem).

Poznámka: skutečná míra paralelnosti SIMD operací se na různých ARM jádrech lišila. Například na Cortex-A8 se sice stále daly provádět operace se 128bitovými registry obsahujícími prvky vektorů (viz navazující kapitoly), ovšem ve skutečnosti se v daný okamžik zpracovávalo jen 64 bitů, tj. každá operace se musela provádět dvakrát. Naproti tomu na Cortex-15 se již zpracovávaly celé 128bitové registry.

2. Podpora technologie NEON v ARMovských jádrech AArch64

Na základě specifikace by všechny standardní implementace jader ARMv8 (s instrukční sadou A64 a s možností přepnutí do režimu AArch64) měly technologii NEON podporovat, stejně jako operace s FP hodnotami. Jinými slovy – na těchto jádrech už typicky není nutné řešit například problém „hardfp versus softfp“. Ovšem v budoucnosti se pravděpodobně setkáme i se specializovanými jádry nakonfigurovanými odlišným způsobem, například:

  • NEON nebude vůbec podporován.
  • FP operace nebudou vůbec podporovány.
  • NEON+FP budou podporovány, ale bez zpracování výjimek u FP operací.

V tomto článku nás ovšem nejvíce zajímá podpora pro half float. Ta existuje ve dvou verzích:

  • Pouze konverzní operace mezi hodnotami uloženými v registrech H. (viz další kapitolu)
  • Podpora pro základní aritmetické operace prováděné přímo s hodnotami typu half float
Poznámka: kvůli tomu, že podpora pro typy half float nemusí být vždy plně realizována, vyžaduje překladač GCC přesné uvedení architektury jádra. V opačném případě nebude výsledek překladu dokonalý, jak ostatně uvidíme v praktické části dnešního článku.

3. Pracovní registry používané instrukcemi NEON

Původní technologie NEON, která byla implementovaná na 32bitových jádrech ARM, používala sadu třiceti dvou nových pracovních registrů, přičemž každý takový registr měl šířku 64 bitů. Tato sada byla oddělena od klasických celočíselných pracovních registrů, což samozřejmě zvýšilo možnosti překladače při optimalizacích kódu (a teoreticky to mohlo umožnit souběžné provádění výpočtů). Nové registry byly pojmenovány D0D31 (písmeno D v tomto případě značí double), popř. mohly být vždy dva sousední registry spojeny do jednoho 128bitového registru (ty byly pojmenovány Q0Q15, písmeno Q v tomto případě znamená quad). Pokud jádro kromě NEONu podporovalo i výše zmíněnou VFP (konkrétně VFPv3 nebo VFPv4), byly registry D0D31 sdíleny mezi oběma jednotkami.

U jader ARMv8-A s novou instrukční sadou A64 došlo v této oblasti k poměrně důležitému vylepšení, protože programátoři mají nově k dispozici 32 pracovních registrů, ovšem nyní se jedná o plnohodnotné 128bitové registry. Tyto registry jsou současně používány i při běžných matematických operacích s hodnotami uloženými v systému plovoucí řádové čárky a taktéž kryptografickým modulem. Došlo ještě k další změně – již nedochází k rozdělení jednoho registru pro typ double do dvou registrů pro hodnotu typu single/float, jako tomu bylo u VFP. Nově je možné každý registr použít jako 128bitový vektor, pro uložení hodnoty double (spodních 64 bitů), uložení hodnoty typu single/float (spodních 32 bitů) popř. (což nás dnes zajímá nejvíce) pro uložení hodnoty typu half float (spodních 16 bitů).

Horní bity jsou při čtení ignorovány, při zápisu nulovány:

Používá se následující pojmenování registrů:

Jméno Význam
v0..v31 128bitové registry (využívá se celá jejich bitová šířka)
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
h0..h31 spodních 16 bitů registrů v0..v31, použito pro hodnoty typu half float

4. Podporované formáty prvků vektorů

Ještě před popisem toho, jakým způsobem se registry používají ve funkci vektorů, se zmiňme o použitých výrazech. U technologie NEON se používá následující terminologie:

  1. Vector vždy značí 64bitovou či 128bitovou část pracovního registru Vn, která je rozdělena na prvky různých typů (s obecně menším počtem bitů, než má celý vektor).
  2. Element je prvek vektoru.
  3. Lane označuje index prvku vektoru. U mnoha operací se kombinují prvky z různých vektorů, které mají shodný index. Příkladem je součet vektorů, který je (logicky) prováděn po odpovídajících si prvcích.

Indexy jednotlivých lanes se zvyšují směrem od nejnižšího bitu k bitu nejvyššímu. Pro pracovní registry V0V31 a použitý typ prvků mohou indexy nabývat těchto hodnot:

Vektor Indexy jednotlivých lanes
128bitový registr ×
2×64 bitů 1, 0
4×32 bitů 3, 2, 1, 0
8×16 bitů 7, 6, ..0
16×8 bitů 15..0

Víme již, že je možné namísto 128bitových vektorů používat i vektory 64bitové, tj. registrové aliasy D0D31. Zde samozřejmě bude k dispozici jen polovina indexů, resp. jen polovina prvků vektorů:

Vektor Indexy jednotlivých lanes
64bitový registr ×
1×64 bitů 0
2×32 bitů 1, 0
4×16 bitů 3, 2, 1, 0
8×8 bitů 7, 6, ..0

5. Vrácení hodnoty prvního resp. druhého parametru typu half float

Nejprve si ukážeme naprosté základy práce s hodnotami typu half float, které jsou v jazyku C popsány jako typ _Float16 (můžete se ovšem setkat i s použitím __fp16). Hodnoty typu half float jsou předávány přes registry Vxx popsané výše: první parametr v registru V0, druhý v registru V1 atd. Návratová hodnota typu half float (pokud funkce hodnotu tohoto typu vrací) je uložena v registru V0:

_Float16 fp16_first(_Float16 x, _Float16 y) {
    return x;
}
 
_Float16 fp16_second(_Float16 x, _Float16 y) {
    return y;
}

První z těchto funkcí je přeložena do jediné instrukce RET, protože jak první parametr, tak i výsledek funkce je předáván přes stejný registr V0:

fp16_first:
        ret

Druhá funkce obsahuje pouze operaci přesunu jedné hodnoty typu half float z registru V1 (druhý parametr) do registru V0 (návratová hodnota):

fp16_second:
        mov     v0.h[0], v1.h[0]
        ret

6. Překlad operace součtu dvou hodnot typu half float

Pokusme se nyní přeložit velmi jednoduchou funkci, která provede součet dvojice hodnot typu half float. Tento typ se v GCC jmenuje _Float16. Realizace takové funkce je triviální:

_Float16 fp16_add(_Float16 x, _Float16 y) {
    _Float16 z = x + y;
    return z;
}

Překlad pro platformu AArch64 může dopadnout následovně:

fp16_add:
        fcvt    s1, h1
        fcvt    s0, h0
        fadd    s0, s0, s1
        fcvt    h0, s0
        ret
Poznámka: při překladu bude vždy zvolen maximální stupeň optimalizace.

Povšimněte si, že se nejdříve instrukcemi FCVT provede převod obou operandů předaných v registrech H0 a H1 na typ single, následně se instrukci FADD provede součet obou hodnot typu single a posléze se provede převod zpět na typ half float, a to opět instrukcí FCVT.

7. Překlad využívající instrukce pro přímé operace s hodnotami typu half float

Výše uvedený výsledek překladu pochopitelně není ideální, protože se provádí konverze z typu half float na single a zpět. Ovšem pokud překladači naznačíme, že cílová platforma umožňuje přímé provádění (některých) operací s hodnotami typu half float, bude situace odlišná. Podpora pro half float byla přidána do ARMv8.2, kterou mnohá existující ARM jádra splňují. Upravíme tedy příkaz pro překlad – předáme následující přepínače:

-O9 -ffast-math -march=armv8.2-a+fp16

Nyní se ve vygenerovaném strojovém kódu nebudou žádné konverze provádět a pouze se přímo sečtou obě předané hodnoty typu half float s tím, že výsledkem je opět hodnota stejného typu:

fp16_add(_Float16, _Float16):
        fadd    h0, h0, h1
        ret

8. Způsob překladu všech čtyř základních aritmetických operací nad hodnotami typu half float

Pro úplnost se ještě podívejme na to, jakým způsobem se přeloží všechny čtyři základní aritmetické operace, tj. nikoli pouze operace součtu, ale i rozdílu, součinu a podílu (modulo není pro typy s plovoucí řádovou čárkou definována):

_Float16 fp16_add(_Float16 x, _Float16 y) {
    return x+y;
}

_Float16 fp16_sub(_Float16 x, _Float16 y) {
    return x-y;
}

_Float16 fp16_mul(_Float16 x, _Float16 y) {
    return x*y;
}

_Float16 fp16_div(_Float16 x, _Float16 y) {
    return x/y;
}

Překlad v případě, že nebudeme přímo a explicitně vyžadovat použití instrukcí half float, bude opět používat převody mezi typy half float a single:

fp16_add:
        fcvt    s1, h1
        fcvt    s0, h0
        fadd    s0, s0, s1
        fcvt    h0, s0
        ret
 
fp16_sub:
        fcvt    s1, h1
        fcvt    s0, h0
        fsub    s0, s0, s1
        fcvt    h0, s0
        ret
 
fp16_mul:
        fcvt    s1, h1
        fcvt    s0, h0
        fmul    s0, s0, s1
        fcvt    h0, s0
        ret
 
fp16_div:
        fcvt    s1, h1
        fcvt    s0, h0
        fdiv    s0, s0, s1
        fcvt    h0, s0
        ret

Pokud naopak použijeme přepínač -march=armv8.2-a+fp16 (nebo i vyšší verzi architektury), bude výsledkem překladu skutečně optimalizovaný strojový kód:

fp16_add(_Float16, _Float16):
        fadd    h0, h0, h1
        ret
 
fp16_sub(_Float16, _Float16):
        fsub    h0, h0, h1
        ret
 
fp16_mul(_Float16, _Float16):
        fmul    h0, h0, h1
        ret
 
fp16_div(_Float16, _Float16):
        fdiv    h0, h0, h1
        ret

9. Překlad operací pro porovnání dvou hodnot typu half float

Kromě provádění základních aritmetických operací se v praxi velmi často porovnávají dvě hodnoty na relaci „je rovno“, „nerovno“, „větší než“, „větší nebo rovno“, „menší než“ a „menší nebo rovno“. Programovací jazyk C má pro tyto operace vyhrazenu šestici operátorů a v případě GCC je možné tyto operace provádět i s hodnotami typu half float (v GCC _Float16).

Ostatně si můžeme tyto operace jednoduše otestovat:

bool fp16_eq(_Float16 x, _Float16 y) {
    return x == y;
}
 
bool fp16_ne(_Float16 x, _Float16 y) {
    return x != y;
}
 
bool fp16_gt(_Float16 x, _Float16 y) {
    return x > y;
}
 
bool fp16_ge(_Float16 x, _Float16 y) {
    return x >= y;
}
 
bool fp16_lt(_Float16 x, _Float16 y) {
    return x < y;
}
 
bool fp16_le(_Float16 x, _Float16 y) {
    return x <= y;
}

Překlad bez povolení přímých manipulací s hodnotami typu half float bude opět založen na konverzi těchto hodnot na typy single. Následně je provedena instrukce FCMP nebo FCMPE a do registru W0 je uložen výsledek porovnání (získaný z příznakových bitů):

fp16_eq(_Float16, _Float16):
        fcvt    s0, h0
        fcvt    s1, h1
        fcmp    s0, s1
        cset    w0, eq
        ret
 
fp16_ne(_Float16, _Float16):
        fcvt    s0, h0
        fcvt    s1, h1
        fcmp    s0, s1
        cset    w0, ne
        ret
 
fp16_gt(_Float16, _Float16):
        fcvt    s0, h0
        fcvt    s1, h1
        fcmpe   s0, s1
        cset    w0, gt
        ret
 
fp16_ge(_Float16, _Float16):
        fcvt    s0, h0
        fcvt    s1, h1
        fcmpe   s0, s1
        cset    w0, ge
        ret
 
fp16_lt(_Float16, _Float16):
        fcvt    s0, h0
        fcvt    s1, h1
        fcmpe   s0, s1
        cset    w0, mi
        ret
 
fp16_le(_Float16, _Float16):
        fcvt    s0, h0
        fcvt    s1, h1
        fcmpe   s0, s1
        cset    w0, ls
        ret

Mohlo by se zdát, že pokud povolíme přímé manipulace s typem half float, bude výsledný strojový kód kratší (a tím pádem i rychlejší), tj. vynechají se konverzní instrukce FCVT atd. Ovšem není tomu tak, o čemž se můžeme snadno přesvědčit:

fp16_eq(_Float16, _Float16):
        fcvt    s0, h0
        fcvt    s1, h1
        fcmp    s0, s1
        cset    w0, eq
        ret
 
fp16_ne(_Float16, _Float16):
        fcvt    s0, h0
        fcvt    s1, h1
        fcmp    s0, s1
        cset    w0, ne
        ret
 
fp16_gt(_Float16, _Float16):
        fcvt    s0, h0
        fcvt    s1, h1
        fcmpe   s0, s1
        cset    w0, gt
        ret
 
fp16_ge(_Float16, _Float16):
        fcvt    s0, h0
        fcvt    s1, h1
        fcmpe   s0, s1
        cset    w0, ge
        ret
 
fp16_lt(_Float16, _Float16):
        fcvt    s0, h0
        fcvt    s1, h1
        fcmpe   s0, s1
        cset    w0, mi
        ret
 
fp16_le(_Float16, _Float16):
        fcvt    s0, h0
        fcvt    s1, h1
        fcmpe   s0, s1
        cset    w0, ls
        ret

10. NEON a operace nad celými vektory

Instrukce NEON na 64bitových mikroprocesorech AArch64 podporují vektory s elementy (prvky) těchto typů:

Typ Šířka Poznámka
float 32 bitů hodnota s plovoucí řádovou čárkou, jednoduchá přesnost
double 64 bitů hodnota s plovoucí řádovou čárkou, dvojitá přesnost
half 32 bitů hodnota s plovoucí řádovou čárkou, poloviční přesnost (jen převody)
     
int8 8 bitů osmibitové celé číslo se znaménkem, obecné použití
int16 16 bitů 16bitové celé číslo se znaménkem, obecné použití
int32 32 bitů 32bitové celé číslo se znaménkem, obecné použití
int64 64 bitů 64bitové celé číslo se znaménkem, obecné použití
     
uint8 8 bitů osmibitové celé číslo bez znaménka, typicky barvová složka pixelu
uint16 16 bitů 16bitové celé číslo bez znaménka, typicky zvukový vzorek
uint32 32 bitů 32bitové celé číslo bez znaménka, obecné použití
uint64 64 bitů 64bitové celé číslo bez znaménka, obecné použití
     
polynomial 8 bitů používán pro výpočty korekcí chyb atd.
polynomial 16 bitů používán pro výpočty korekcí chyb atd.
Poznámka: některé typy (formáty) jsou dostupné jen pro NEON implementovaný na mikroprocesorech s architekturou AArch64. Týká se to zejména použití vektorů s elementy typu double.

Nás nyní budou zajímat především „vektorizace“ operací prováděných na poli obsahujícími prvky typu half float.

11. Přičtení konstanty ke všem prvkům pole

V dalším ukázkovém příkladu je realizována funkce, která ke všem prvkům pole s prvky typu half float o známé délce přičítá konstantu předanou formou argumentu. Celý příklad je naprogramován naivním způsobem, bez jakékoli snahy o optimalizace na úrovni céčkovského zdrojového kódu:

#define LENGTH 32
 
void fp16_delta(_Float16 values[LENGTH], _Float16 delta) {
    int i;
    for (i=0; i<LENGTH; i++) {
        values[i] += delta;
    }
}

V případě, že není povoleno rozšíření instrukční sady o fp16, bude výsledkem překladu explicitně vygenerovaná programová smyčka, ve které se (v každé iteraci) provádí dvojice konverzí instrukcí FCVT: z half float na single a naopak ze single na half float. Výsledek tedy nebude příliš rychlý:

fp16_delta:
        fcvt    s0, h0
        add     x1, x0, 64
.L2:
        ldr     h31, [x0]
        fcvt    s31, h31
        fadd    s31, s31, s0
        fcvt    h31, s31
        str     h31, [x0], 2
        cmp     x0, x1
        bne     .L2
        ret

Pokud jsou operace fp16 povoleny, bude překlad proveden odlišným způsobem. Součet 32 prvků pole je rozdělen do čtyř vektorových instrukcí FADD. Povšimněte si, jak se „role“ registrů mění pro různé operace – někdy se pracuje s celými 128bitovými registry Qx, jindy se stejným registrem, ovšem tak, že jeho obsah bude považován za vektor osmi 16bitových hodnot typu half float (Vx.8h):

fp16_delta(_Float16*, _Float16):
        dup     v0.8h, v0.h[0]
        ldp     q3, q2, [x0]
        ldp     q1, q31, [x0, 32]
        fadd    v3.8h, v3.8h, v0.8h
        fadd    v2.8h, v2.8h, v0.8h
        fadd    v1.8h, v1.8h, v0.8h
        fadd    v31.8h, v31.8h, v0.8h
        stp     q3, q2, [x0]
        stp     q1, q31, [x0, 32]
        ret
Poznámka: instrukce LDP a STP dokážou pracovat s párem registrů – je to částečná náhrada za instrukce LDM a STM, které v instrukční sadě A64 neexistují.

12. Součet odpovídajících si prvků polí typu half float

Zajímavé bude taktéž zjistit, jakým způsobem bude přeložena funkce, která provede součet odpovídajících si prvků polí, tedy vlastně klasický vektorový součet. Pokud si tento problém značně zjednodušíme tím, že nebudeme předávat délku polí (a kontrolovat, zda mají obě pole stejnou délku atd.) ani používat přístup k prvkům přes ukazatele, je možné součet napsat následovně:

#define SIZE 32
 
void add_arrays(_Float16 *a, _Float16 *b) {
    int i;
    for (i=0; i<SIZE; i++) {
        a[i] += b[i];
    }
}

Pokud nebude povolena přímá manipulace s hodnotami typu half float, bude překlad proveden do formy počítané programové smyčky, zde konkrétně s počitadlem uloženým v registru X2, které se zvyšuje o dvojku, protože je tento registr současně použitý pro výpočet offsetu:

        mov     x2, 0
.L2:
        ...
        ...
        ...
        add     x2, x2, 2
        cmp     x2, 64
        bne     .L2

Uvnitř těla smyčky se oba prvky načtou z adres X0+X2 a X1+X2, převedou na hodnoty typu single, sečtou a uloží na adresu X0+X2:

        ldr     h31, [x0, x2]
        ldr     h30, [x1, x2]
        fcvt    s31, h31
        fcvt    s30, h30
        fadd    s30, s31, s30
        fcvt    h30, s30
        str     h30, [x0, x2]

Výsledek vypadá následovně:

add_arrays(_Float16*, _Float16*):
        mov     x2, 0
.L2:
        ldr     h31, [x0, x2]
        ldr     h30, [x1, x2]
        fcvt    s31, h31
        fcvt    s30, h30
        fadd    s30, s31, s30
        fcvt    h30, s30
        str     h30, [x0, x2]
        add     x2, x2, 2
        cmp     x2, 64
        bne     .L2
        ret

Při povolení přímé manipulace s hodnotami typu half float překladač zjistí, jestli se pole nepřekrývají. Pokud ano, skočí do běžné smyčky, ve které se již neprovádí převody hodnot:

        mov     x2, 0
.L2:
        ldr     h23, [x0, x2]
        ldr     h22, [x1, x2]
        fadd    h22, h23, h22
        str     h22, [x0, x2]
        add     x2, x2, 2
        cmp     x2, 64
        bne     .L2

Pokud se smyčky nepřekrývají, je celý výpočet rozbalen a proveden po vektorech se čtyřmi prvky:

        ldr     q24, [x1]
        ldp     q25, q27, [x0]
        ldp     q29, q31, [x0, 32]
        fadd    v24.8h, v25.8h, v24.8h
        str     q24, [x0]
        ldr     q26, [x1, 16]
        fadd    v26.8h, v27.8h, v26.8h
        str     q26, [x0, 16]
        ldr     q28, [x1, 32]
        fadd    v28.8h, v29.8h, v28.8h
        str     q28, [x0, 32]
        ldr     q30, [x1, 48]
        fadd    v30.8h, v31.8h, v30.8h
        str     q30, [x0, 48]

Celkový výsledek vypadá takto:

add_arrays(_Float16*, _Float16*):
        sub     x2, x0, x1
        sub     x2, x2, #2
        cmp     x2, 12
        bls     .L4
        ldr     q24, [x1]
        ldp     q25, q27, [x0]
        ldp     q29, q31, [x0, 32]
        fadd    v24.8h, v25.8h, v24.8h
        str     q24, [x0]
        ldr     q26, [x1, 16]
        fadd    v26.8h, v27.8h, v26.8h
        str     q26, [x0, 16]
        ldr     q28, [x1, 32]
        fadd    v28.8h, v29.8h, v28.8h
        str     q28, [x0, 32]
        ldr     q30, [x1, 48]
        fadd    v30.8h, v31.8h, v30.8h
        str     q30, [x0, 48]
        ret
.L4:
        mov     x2, 0
.L2:
        ldr     h23, [x0, x2]
        ldr     h22, [x1, x2]
        fadd    h22, h23, h22
        str     h22, [x0, x2]
        add     x2, x2, 2
        cmp     x2, 64
        bne     .L2
        ret

13. Pomáháme překladači: klíčové slovo restrict

V případě, že sčítáme dvě rozdílná pole, by bylo vhodné překladači nějakým způsobem napovědět, že nemusí do výsledného kódu vkládat i variantu s překrývajícími se poli. Pokud by byla pole odlišného typu (resp. pokud by ukazatele byly odlišného typu), je řešení snadné – podle normy je chování překladače nedefinované :-). Ovšem naše pole mají shodný typ (přesněji řečeno oba ukazatele mají totožný typ _Float16 *), takže překladač musí předpokládat, že se může jednat o stejné ukazatele nebo že se budou oblasti překrývat. Jak překladači napovědět, že k této situaci nedojde a nemusí se jí zabývat? V jazyku C, konkrétně ve verzi C99 a vyšší, je pro tento účel rezervováno slovo restrict, které se používá následujícím způsobem (pro ANSI C lze v GCC použít i __restrict__):

#define SIZE 32
 
void add_arrays(_Float16 * restrict a, _Float16 * restrict b) {
    int i;
    for (i=0; i<SIZE; i++) {
        a[i] += b[i];
    }
}

Překladač v tomto případě může provést optimalizace předpokládající, že se sčítají odlišná pole a výsledek bude mnohem kratší a pro nás i čitelnější – jen čtyři „vektorové“ součty:

add_arrays:
        ldp     q24, q26, [x1]
        ldp     q28, q30, [x1, 32]
        ldp     q25, q27, [x0]
        ldp     q29, q31, [x0, 32]
        fadd    v24.8h, v25.8h, v24.8h
        fadd    v26.8h, v27.8h, v26.8h
        fadd    v28.8h, v29.8h, v28.8h
        fadd    v30.8h, v31.8h, v30.8h
        stp     q24, q26, [x0]
        stp     q28, q30, [x0, 32]
        ret

14. Překlad součtu polí s menším počtem prvků

V případě, že je počet prvků sčítaných polí relativně malý a pokud jsou povoleny maximální optimalizace, může se překladač rozhodnout, že celou smyčku s výpočtem rozbalí. Opět si to pochopitelně otestujeme:

#define SIZE 16
 
void add_arrays(_Float16 *a, _Float16 *b) {
    int i;
    for (i=0; i<SIZE; i++) {
        a[i] += b[i];
    }
}

Překlad za předpokladu, že nejsou povoleny přímé manipulace s prvky typu half float. Došlo k rozbalení smyčky, ovšem stále můžeme vidět stejný vzor: FCVT+FCVF+FADD+FCVT:

add_arrays(_Float16*, _Float16*):
        ldr     h24, [x1]
        ldr     h25, [x0]
        ldr     h27, [x0, 2]
        fcvt    s24, h24
        ldr     h29, [x0, 4]
        fcvt    s25, h25
        ldr     h31, [x0, 6]
        fcvt    s27, h27
        ldr     h1, [x0, 8]
        fcvt    s29, h29
        ldr     h3, [x0, 10]
        fcvt    s31, h31
        ldr     h5, [x0, 12]
        fadd    s24, s25, s24
        fcvt    s1, h1
        fcvt    s3, h3
        ldr     h7, [x0, 14]
        fcvt    s5, h5
        ldr     h17, [x0, 16]
        ldr     h19, [x0, 18]
        fcvt    h24, s24
        fcvt    s7, h7
        fcvt    s17, h17
        ldr     h21, [x0, 20]
        fcvt    s19, h19
        str     h24, [x0]
        fcvt    s21, h21
        ldr     h26, [x1, 2]
        fcvt    s26, h26
        fadd    s26, s27, s26
        fcvt    h26, s26
        str     h26, [x0, 2]
        ldr     h28, [x1, 4]
        fcvt    s28, h28
        fadd    s28, s29, s28
        fcvt    h28, s28
        str     h28, [x0, 4]
        ldr     h30, [x1, 6]
        fcvt    s30, h30
        fadd    s30, s31, s30
        fcvt    h30, s30
        str     h30, [x0, 6]
        ldr     h0, [x1, 8]
        fcvt    s0, h0
        fadd    s0, s1, s0
        fcvt    h0, s0
        str     h0, [x0, 8]
        ldr     h2, [x1, 10]
        fcvt    s2, h2
        fadd    s2, s3, s2
        fcvt    h2, s2
        str     h2, [x0, 10]
        ldr     h4, [x1, 12]
        fcvt    s4, h4
        fadd    s4, s5, s4
        fcvt    h4, s4
        str     h4, [x0, 12]
        ldr     h6, [x1, 14]
        fcvt    s6, h6
        fadd    s6, s7, s6
        fcvt    h6, s6
        str     h6, [x0, 14]
        ldr     h16, [x1, 16]
        fcvt    s16, h16
        fadd    s16, s17, s16
        fcvt    h16, s16
        str     h16, [x0, 16]
        ldr     h18, [x1, 18]
        fcvt    s18, h18
        fadd    s18, s19, s18
        fcvt    h18, s18
        str     h18, [x0, 18]
        ldr     h20, [x1, 20]
        fcvt    s20, h20
        fadd    s20, s21, s20
        fcvt    h20, s20
        str     h20, [x0, 20]
        ldr     h22, [x1, 22]
        ldr     h23, [x0, 22]
        ldr     h25, [x0, 24]
        fcvt    s22, h22
        ldr     h27, [x0, 26]
        fcvt    s23, h23
        ldr     h29, [x0, 28]
        fcvt    s25, h25
        ldr     h31, [x0, 30]
        fcvt    s27, h27
        fcvt    s29, h29
        fadd    s22, s23, s22
        fcvt    s31, h31
        fcvt    h22, s22
        str     h22, [x0, 22]
        ldr     h24, [x1, 24]
        fcvt    s24, h24
        fadd    s24, s25, s24
        fcvt    h24, s24
        str     h24, [x0, 24]
        ldr     h26, [x1, 26]
        fcvt    s26, h26
        fadd    s26, s27, s26
        fcvt    h26, s26
        str     h26, [x0, 26]
        ldr     h28, [x1, 28]
        fcvt    s28, h28
        fadd    s28, s29, s28
        fcvt    h28, s28
        str     h28, [x0, 28]
        ldr     h30, [x1, 30]
        fcvt    s30, h30
        fadd    s30, s31, s30
        fcvt    h30, s30
        str     h30, [x0, 30]
        ret

Definice, že se pole nepřekrývají + povolení práce s hodnotami typu half float vede k mnohem kratšímu a pochopitelně i rychlejšímu kódu:

add_arrays:
        ldp     q29, q31, [x0]
        ldp     q28, q30, [x1]
        fadd    v28.8h, v29.8h, v28.8h
        fadd    v30.8h, v31.8h, v30.8h
        stp     q28, q30, [x0]
        ret

15. Intrinsic pro provádění operací s hodnotami typu half float

Intrinsic, které se taktéž v některých dokumentech označují možná přiléhavějším slovem built-ins, jsou z pohledu vývojáře (a taktéž z pohledu syntaxe jazyka C) funkce, které jsou rozeznávány a implementovány přímo překladačem (v našem konkrétním případě překladačem programovacího jazyka C), aniž by musely být deklarovány ve vyvíjeném programu nebo aniž by byly součástí nějakých knihoven. Překladač tedy nemusí generovat kód pro načtení runtime knihovny s těmito pseudofunkcemi, řešit volání těchto pseudofunkcí, ale naopak může využít všechny v dané situaci dostupné optimalizační strategie (typicky se intrinsic do kódu vkládá jako sekvence strojových instrukcí).

V předchozím odstavci je sice napsáno, že intrinsic (built-ins) vypadají z pohledu syntaxe programovacího jazyka C jako běžné funkce (a tak je tedy bude používat vývojář popř. vývojové prostředí), ovšem ve skutečnosti se o plnohodnotné funkce nejedná, protože strojový kód pro ně generuje přímo překladač na základě interních pravidel. Do jisté míry se intrinsic podobají inline funkcím, protože i u nich lze zcela odstranit problematické předávání parametrů a pamatování návratových hodnot.

Proč však vlastně intrinsic vznikly a proč se používají zrovna v programovacím jazyku C, který je v ostatních ohledech navržen takovým způsobem, aby byl prakticky zcela abstrahován (na rozdíl od například Pascalu nebo i Go) od konkrétních knihoven a funkcí? V některých případech je nutné umožnit programátorům přístup ke specializovaným instrukcím, jejichž sémantiku není možné dobře vyjádřit přímo v C. A přesně toto je případ SIMD instrukcí, ať již na platformě x86–64, tak i na ARMech či na RISC-V. Z tohoto důvodu se budeme intrinsic zabývat v dalším textu; zaměříme se přitom opět na GCC, jehož vlastnosti jsou postupně přebírány i do Clangu.

Intrinsic pro architekturu ARM32 i AArch64 naleznete na adrese https://developer.arm.com/ar­chitectures/instruction-sets/intrinsics/. V GCC sice mohou některé z popsaných intrinsic chybět, ovšem ty základní lze použít i v tomto překladači jazyka C.

16. Realizace součtu dvou hodnot typu half float přes intrinsic funkci

Podívejme se na velmi jednoduchý příklad použití intrinic funkce. Konkrétně se bude jednat o funkci vaddh_f16 pro provedení součtu hodnot typu half float:

#include <arm_fp16.h>
 
_Float16 fp16_add(_Float16 x, _Float16 y) {
    return vaddh_f16(x, y);
}

Výsledek překladu by měl vypadat následovně:

fp16_add(_Float16, _Float16):
        fadd    h0, h0, h1
        ret
Poznámka: v tomto případě překladač ohlídá, že může použít instrukce pro práci s half float. Pokud nejsou povoleny, je o tom vývojář informován:
include/arm_fp16.h: In function 'add_arrays':
include/arm_fp16.h:421:1: error: inlining failed in call to 'always_inline' 'vaddh_f16': target specific option mismatch
  421 | vaddh_f16 (float16_t __a, float16_t __b)
      | ^~~~~~~~~
:6:12: note: called from here
    6 |     return vaddh_f16(a, b);
      |            ^~~~~~~~~~~~~~~

17. Základní aritmetické operace s hodnotami typu half float realizované přes intrinsic

Instrinsic pochopitelně existují i pro další tři základní aritmetické operace, takže je můžeme snadno použít:

#include <arm_fp16.h>
 
_Float16 fp16_add(_Float16 x, _Float16 y) {
    return vaddh_f16(x, y);
}
 
_Float16 fp16_sub(_Float16 x, _Float16 y) {
    return vsubh_f16(x, y);
}
 
_Float16 fp16_mul(_Float16 x, _Float16 y) {
    return vmulh_f16(x, y);
}
 
_Float16 fp16_div(_Float16 x, _Float16 y) {
    return vdivh_f16(x, y);
}

Výsledky ukazují, že jsou vygenerovány základní instrukce pro zpracování hodnot typu half float:

fp16_add(_Float16, _Float16):
        fadd    h0, h0, h1
        ret
 
fp16_sub(_Float16, _Float16):
        fsub    h0, h0, h1
        ret
 
fp16_mul(_Float16, _Float16):
        fmul    h0, h0, h1
        ret
 
fp16_div(_Float16, _Float16):
        fdiv    h0, h0, h1
        ret

18. Porovnání operandů typu half float realizované přes intrinsic

Mezi další intrinsic, které je možné přímo zavolat, patří vestavěné funkce určené pro porovnání dvou hodnot typu half float. V jejich názvu je vždy zapsána testovaná relace (EQ=equal atd.) a mají příponu _f16. Opět si vyzkoušejme základní způsob jejich použití:

#include <arm_fp16.h>
 
bool fp16_eq(_Float16 x, _Float16 y) {
    return vceqh_f16(x, y);
}
 
bool fp16_ne(_Float16 x, _Float16 y) {
    return !vceqh_f16(x, y);
}
 
bool fp16_gt(_Float16 x, _Float16 y) {
    return vcgth_f16(x, y);
}
 
bool fp16_ge(_Float16 x, _Float16 y) {
    return vcgeh_f16(x, y);
}
 
bool fp16_lt(_Float16 x, _Float16 y) {
    return vclth_f16(x, y);
}
 
bool fp16_le(_Float16 x, _Float16 y) {
    return vcleh_f16(x, y);
}

Z výsledků je patrné, že překlad je proveden odlišně od překladu z deváté kapitoly. Nejprve je zavolána instrukce FCMEQ, FCMGT nebo FCMGE, které provedou porovnání a v případě, že je testovaná podmínka splněna, nastaví se všech šestnáct registrů cílového registru na jedničky (pokud podmínka splněna naopak není, jsou všechny bity vynulovány). Následně je oněch šestnáct bitů přeneseno do celočíselného registru a je proveden test jeho hodnoty (popř. se výsledek zneguje, protože jen třemi typy porovnání musíme realizovat všech šest testů na relaci):

fp16_eq(_Float16, _Float16):
        fcmeq   h31, h0, h1
        umov    w0, v31.h[0]
        tst     w0, 65535
        cset    w0, ne
        ret
 
fp16_ne(_Float16, _Float16):
        fcmeq   h31, h0, h1
        umov    w0, v31.h[0]
        tst     w0, 65535
        cset    w0, eq
        ret
 
fp16_gt(_Float16, _Float16):
        fcmgt   h31, h0, h1
        umov    w0, v31.h[0]
        tst     w0, 65535
        cset    w0, ne
        ret
 
fp16_ge(_Float16, _Float16):
        fcmge   h31, h0, h1
        umov    w0, v31.h[0]
        tst     w0, 65535
        cset    w0, ne
        ret
 
fp16_lt(_Float16, _Float16):
        fcmgt   h31, h1, h0
        umov    w0, v31.h[0]
        tst     w0, 65535
        cset    w0, ne
        ret
 
fp16_le(_Float16, _Float16):
        fcmge   h31, h1, h0
        umov    w0, v31.h[0]
        tst     w0, 65535
        cset    w0, ne
        ret
Poznámka: popravdě si nejsem v tomto případě jistý, že je výsledná sekvence strojových instrukcí zvolena nejlépe.

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

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

# Příklad Stručný popis Adresa
1 fp16_add.c operace součtu dvou hodnot typu half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_add.c
2 fp16_add.asm překlad operace součtu dvou hodnot typu half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_add.asm
3 fp16_add_fp16.asm překlad využívající instrukce pro přímé operace s hodnotami typu half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_add_fp16.asm
       
4 fp16_arith.c všechny čtyři základní aritmetické operace nad typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_arith.c
5 fp16_arith.asm překlad všech čtyř základních aritmetických operací https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_arith.asm
6 fp16_arith_fp16.asm překlad všech čtyř základních aritmetických operací s přímými instrukcemi pro half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_a­rith_fp16.asm
       
7 fp16_comparison.c realizace všech šesti operací pro porovnání dvou hodnot typu half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_comparison.c
8 fp16_comparison.asm překlad operací pro porovnání dvou hodnot typu half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_com­parison.asm
9 fp16_comparison_fp16.asm překlad operací pro porovnání dvou hodnot typu half float s přímými instrukcemi https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_com­parison_fp16.asm
       
10 fp16_add_delta.c přičtení konstanty ke všem prvkům pole https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_add_delta.c
11 fp16_add_delta.asm překlad do strojového kódu bez přímého povolení manipulace s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_add_delta.asm
12 fp16_add_delta_fp16.asm překlad do strojového kódu s přímým povolení manipulace s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_delta_fp16.asm
       
13 fp16_add_arrays32.c součet odpovídajících si prvků polí typu half float se známou délkou https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_arrays32.c
14 fp16_add_arrays32.asm překlad do strojového kódu bez přímého povolení manipulace s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_arrays32.asm
15 fp16_add_arrays32_fp16.asm překlad do strojového kódu s přímým povolení manipulace s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_arrays32_fp16.asm
16 fp16_add_arrays32_restrict.c součet odpovídajících si prvků polí typu half float, zajištění, že se pole nepřekrývají https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_arrays32_restrict.c
17 fp16_add_arrays32_restrict.asm překlad do strojového kódu s přímým povolení manipulace s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_arrays32_restrict.asm
       
18 fp16_add_arrays16.c součet odpovídajících si prvků polí typu half float se známou délkou https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_arrays16.c
19 fp16_add_arrays16.asm překlad do strojového kódu bez přímého povolení manipulace s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_arrays16.asm
20 fp16_add_arrays16_fp16.asm překlad do strojového kódu s přímým povolení manipulace s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_arrays16_fp16.asm
21 fp16_add_arrays16_restrict.c součet odpovídajících si prvků polí typu half float, zajištění, že se pole nepřekrývají https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_arrays16_restrict.c
22 fp16_add_arrays16_restrict.asm https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_arrays16_restrict.asm
       
23 fp16_first_second.c funkce vracející svůj první nebo naopak druhý parametr https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_fir­st_second.c
24 fp16_first_second.asm překlad do strojového kódu s přímým povolení manipulace s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_fir­st_second.asm
       
25 fp16_add_intrinsic.c realizace součtu dvou hodnot typu half float přes intrinsic funkci, zdrojový kód v jazyku C https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_intrinsic.c
26 fp16_add_intrinsic.asm realizace součtu dvou hodnot typu half float přes intrinsic funkci, výsledek překladu https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_intrinsic.asm
       
27 fp16_arith_intrinsic.c základní aritmetické operace s hodnotami typu half float realizované přes intrinsic, zdrojový kód v jazyku C https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_a­rith_intrinsic.c
28 fp16_arith_intrinsic.asm základní aritmetické operace s hodnotami typu half float realizované přes intrinsic, výsledek překladu https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_a­rith_intrinsic.asm
       
29 fp16_comparison_intrinsic.c porovnání operandů typu half float realizované přes intrinsic, zdrojový kód v jazyku C https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_com­parison_intrinsic.c
30 fp16_comparison_intrinsic.asm porovnání operandů typu half float realizované přes intrinsic, výsledek překladu https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_com­parison_intrinsic.asm

20. Odkazy na Internetu

  1. NEON Technology (stránky ARM)
    https://developer.arm.com/techno­logies/neon
  2. SIMD Assembly Tutorial: ARM NEON – Xiph.org
    https://people.xiph.org/~tte­rribe/daala/neon_tutorial­.pdf
  3. Ne10
    http://projectne10.github.io/Ne10/
  4. NEON and Floating-Point architecture
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.den0024a/BABIGHEB.html
  5. An Introduction to ARM NEON
    http://peterdn.com/post/an-introduction-to-ARM-NEON.aspx
  6. ARM NEON Intrinsics Reference
    http://infocenter.arm.com/hel­p/topic/com.arm.doc.ihi0073a/I­HI0073A_arm_neon_intrinsic­s_ref.pdf
  7. Arm Neon Intrinsics vs hand assembly
    https://stackoverflow.com/qu­estions/9828567/arm-neon-intrinsics-vs-hand-assembly
  8. ARM NEON Optimization. An Example
    http://hilbert-space.de/?p=22
  9. AArch64 NEON instruction format
    https://developer.arm.com/doc­s/den0024/latest/7-aarch64-floating-point-and-neon/73-aarch64-neon-instruction-format
  10. Vektorové procesory aneb další pokus o zvýšení výpočetního výkonu počítačů
    http://www.root.cz/clanky/vektorove-procesory-aneb-dalsi-pokus-o-zvyseni-vypocetniho-vykonu-pocitacu/
  11. SIMD instrukce využívané v moderních mikroprocesorech řady x86
    http://www.root.cz/clanky/simd-instrukce-vyuzivane-v-modernich-mikroprocesorech-rady-x86/
  12. SIMD instrukce v moderních mikroprocesorech řady x86 (2.část: SSE)
    http://www.root.cz/clanky/simd-instrukce-v-modernich-mikroprocesorech-rady-x86–2-cast-sse/
  13. SIMD instrukce v moderních mikroprocesorech řady x86 (3.část: SSE2)
    http://www.root.cz/clanky/simd-instrukce-v-modernich-mikroprocesorech-rady-x86–3-cast-sse2/
  14. Instrukce typu SIMD na mikroprocesorech RISC
    http://www.root.cz/clanky/instrukce-typu-simd-na-mikroprocesorech-risc/
  15. Instrukce typu SIMD na mikroprocesorech RISC (2. část)
    http://www.root.cz/clanky/instrukce-typu-simd-na-mikroprocesorech-risc-2-cast/
  16. Instrukce typu SIMD na mikroprocesorech RISC (3.část – MIPS-3D a VIS)
    http://www.root.cz/clanky/instrukce-typu-simd-na-mikroprocesorech-risc-3-cast-mips-3d-a-vis/
  17. Trasování a ladění nativních aplikací v Linuxu
    https://www.root.cz/clanky/trasovani-a-ladeni-nativnich-aplikaci-v-linuxu/
  18. Trasování a ladění nativních aplikací v Linuxu: použití GDB a jeho nadstaveb
    https://www.root.cz/clanky/trasovani-a-ladeni-nativnich-aplikaci-v-linuxu-pouziti-gdb-a-jeho-nadstaveb/
  19. Debuggery a jejich nadstavby v Linuxu (3): Nemiver
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/
  20. Debuggery a jejich nadstavby v Linuxu (4): KDbg
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/
  21. Debuggery a jejich nadstavby v Linuxu (5): ladění aplikací v editorech Emacs a Vim
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-5-ladeni-aplikaci-v-editorech-emacs-a-vim/
  22. Tracing (software)
    https://en.wikipedia.org/wi­ki/Tracing_%28software%29
  23. cgdb: the curses debugger
    https://cgdb.github.io/
  24. cgdb: dokumentace
    https://cgdb.github.io/docs/cgdb-split.html
  25. strace(1) – Linux man page
    http://linux.die.net/man/1/strace
  26. strace (stránka projektu na SourceForge)
    https://sourceforge.net/pro­jects/strace/
  27. strace (Wikipedia)
    https://en.wikipedia.org/wiki/Strace
  28. GDB – Dokumentace
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/
  29. GDB – Supported Languages
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/Suppor­ted-Languages.html#Supported-Languages
  30. GNU Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Debugger
  31. The LLDB Debugger
    http://lldb.llvm.org/
  32. Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/Debugger
  33. Comparison of ARMv8-A cores
    https://en.wikipedia.org/wi­ki/Comparison_of_ARMv8-A_cores
  34. A64 General Instructions
    http://www.keil.com/suppor­t/man/docs/armclang_asm/ar­mclang_asm_pge1427898258836­.htm
  35. ARMv8 (AArch64) Instruction Encoding
    http://kitoslab-eng.blogspot.cz/2012/10/armv8-aarch64-instruction-encoding.html
  36. Cortex-A32 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a32-processor.php
  37. Cortex-A35 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a35-processor.php
  38. Cortex-A53 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a53-processor.php
  39. Cortex-A57 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a57-processor.php
  40. Cortex-A72 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a72-processor.php
  41. Cortex-A73 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a73-processor.php
  42. Apple A7 (SoC založen na CPU Cyclone)
    https://en.wikipedia.org/wi­ki/Apple_A7
  43. System cally pro AArch64 na Linuxu
    https://github.com/torval­ds/linux/blob/master/inclu­de/uapi/asm-generic/unistd.h
  44. Architectures/AArch64 (FedoraProject.org)
    https://fedoraproject.org/wi­ki/Architectures/AArch64
  45. SIG pro AArch64 (CentOS)
    https://wiki.centos.org/Spe­cialInterestGroup/AltArch/A­Arch64
  46. The ARMv8 instruction sets
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.den0024a/ch05s01.html
  47. A64 Instruction Set
    https://developer.arm.com/pro­ducts/architecture/instruc­tion-sets/a64-instruction-set
  48. Switching between the instruction sets
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.den0024a/ch05s01.html
  49. The A64 instruction set
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.den0024a/ch05s01.html
  50. Introduction to ARMv8 64-bit Architecture
    https://quequero.org/2014/04/in­troduction-to-arm-architecture/
  51. MCU market turns to 32-bits and ARM
    http://www.eetimes.com/do­cument.asp?doc_id=1280803
  52. Cortex-M0 Processor (ARM Holdings)
    http://www.arm.com/produc­ts/processors/cortex-m/cortex-m0.php
  53. Cortex-M0+ Processor (ARM Holdings)
    http://www.arm.com/produc­ts/processors/cortex-m/cortex-m0plus.php
  54. ARM Processors in a Mixed Signal World
    http://www.eeweb.com/blog/arm/arm-processors-in-a-mixed-signal-world
  55. ARM Architecture (Wikipedia)
    https://en.wikipedia.org/wi­ki/ARM_architecture
  56. DSP for Cortex-M
    https://developer.arm.com/techno­logies/dsp/dsp-for-cortex-m
  57. 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
  58. 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
  59. Q (number format)
    https://en.wikipedia.org/wi­ki/Q_%28number_format%29
  60. 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
  61. TriCoreTM V1.6 Instruction Set: 32-bit Unified Processor Core
    http://www.infineon.com/dgdl/tc_v131_in­structionset_v138.pdf?file­Id=db3a304412b407950112b409b6dd0352
  62. TriCore v2.2 C Compiler, Assembler, Linker Reference Manual
    http://tasking.com/suppor­t/tricore/tc_reference_gu­ide_v2.2.pdf
  63. Infineon TriCore (Wikipedia)
    https://en.wikipedia.org/wi­ki/Infineon_TriCore
  64. 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
  65. Comparing four 32-bit soft processor cores
    http://www.eetimes.com/au­thor.asp?section_id=14&doc_id=1286116
  66. RISC-V Instruction Set
    http://riscv.org/download­.html#spec_compressed_isa
  67. RISC-V Spike (ISA Simulator)
    http://riscv.org/download.html#isa-sim
  68. RISC-V (Wikipedia)
    https://en.wikipedia.org/wiki/RISC-V
  69. David Patterson (Wikipedia)
    https://en.wikipedia.org/wi­ki/David_Patterson_(compu­ter_scientist)
  70. OpenRISC (oficiální stránky projektu)
    http://openrisc.io/
  71. OpenRISC architecture
    http://openrisc.io/architecture.html
  72. Emulátor OpenRISC CPU v JavaScriptu
    http://s-macke.github.io/jor1k/demos/main.html
  73. OpenRISC (Wikipedia)
    https://en.wikipedia.org/wi­ki/OpenRISC
  74. OpenRISC – instrukce
    http://sourceware.org/cgen/gen-doc/openrisc-insn.html
  75. OpenRISC – slajdy z přednášky o projektu
    https://iis.ee.ethz.ch/~gmichi/a­socd/lecturenotes/Lecture6­.pdf
  76. Berkeley RISC
    http://en.wikipedia.org/wi­ki/Berkeley_RISC
  77. Great moments in microprocessor history
    http://www.ibm.com/develo­perworks/library/pa-microhist.html
  78. Microprogram-Based Processors
    http://research.microsoft.com/en-us/um/people/gbell/Computer_Struc­tures_Principles_and_Exam­ples/csp0167.htm
  79. Great Microprocessors of the Past and Present
    http://www.cpushack.com/CPU/cpu1.html
  80. A Brief History of Microprogramming
    http://www.cs.clemson.edu/~mar­k/uprog.html
  81. What is RISC?
    http://www-cs-faculty.stanford.edu/~ero­berts/courses/soco/projec­ts/2000–01/risc/whatis/
  82. RISC vs. CISC
    http://www-cs-faculty.stanford.edu/~ero­berts/courses/soco/projec­ts/2000–01/risc/risccisc/
  83. RISC and CISC definitions:
    http://www.cpushack.com/CPU/cpu­AppendA.html
  84. FPGA
    https://cs.wikipedia.org/wi­ki/Programovateln%C3%A9_hra­dlov%C3%A9_pole
  85. The Evolution of RISC
    http://www.ibm.com/develo­perworks/library/pa-microhist.html#sidebar1
  86. List of ARM instructions implementing half-precision floating-point arithmetic
    https://stackoverflow.com/qu­estions/76255632/list-of-arm-instructions-implementing-half-precision-floating-point-arithmetic
  87. Half-Precision Floating Point (GCC)
    https://gcc.gnu.org/online­docs/gcc/Half-Precision.html
  88. Additional Floating Types (GCC)
    https://gcc.gnu.org/online­docs/gcc/Floating-Types.html
  89. Advanced SIMD (Neon)
    https://en.wikipedia.org/wi­ki/ARM_architecture_family#Ad­vanced_SIMD_(NEON)
  90. GCC: ARM options
    https://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html
  91. Compile ARM Neon intrinsics on macos (M3 chipsets) using clang
    https://stackoverflow.com/qu­estions/79056335/compile-arm-neon-intrinsics-on-macos-m3-chipsets-using-clang
  92. Intrinsics – Arm Developer
    https://developer.arm.com/ar­chitectures/instruction-sets/intrinsics/
  93. FCMEQ (register)
    https://www.scs.stanford.e­du/~zyedidia/arm64/fcmeq_ad­vsimd_reg.html
  94. FCMGE (register)
    https://www.scs.stanford.e­du/~zyedidia/arm64/fcmge_ad­vsimd_reg.html
  95. FCMGT (register)
    https://www.scs.stanford.e­du/~zyedidia/arm64/fcmgt_ad­vsimd_reg.html

Autor článku

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