Hlavní navigace

Instrukční sada AArch64 (2.část)

20. 6. 2017
Doba čtení: 23 minut

Sdílet

Dnes budeme pokračovat v popisu instrukční sady AArch64. Zmíníme se zejména o instrukcích matematického koprocesoru a taktéž o SIMD operacích umožňujících paralelní zpracování prvků vektorů. Na závěr si popíšeme volání služeb jádra.

Obsah

1. Instrukční sada AArch64 (2.část)

2. Načtení konstanty do FP registru

3. Přenos operandů mezi registry

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

5. Převod na celá čísla (zaokrouhlení)

6. Základní aritmetické operace

7. Porovnání operandů

8. SIMD operace

9. Operace s prvky vektorů

10. Aritmetické operace s prvky uloženými ve vektorech

11. Doplnění – problematika načítání konstant

12. Volání služeb jádra (syscall) na procesorech AArch64

13. Příklad programu volajícího službu jádra exit

14. Výsledná podoba binárního souboru s přeloženými instrukcemi

15. Odkazy na Internetu

1. Instrukční sada AArch64 (2.část)

Instrukce používané klasickou celočíselnou aritmeticko-logickou jednotkou 64bitových procesorů s architekturou AArch64 jsme si popsali minule, takže se dnes zaměříme na matematický koprocesor a posléze i na „vektorovou jednotku“. Připomeňme si, že u operací s plovoucí řádovou čárkou je používána samostatná sada pracovních registrů pojmenovaných v0v31 s šířkou celých 128 bitů (typ quad), přičemž spodních 64 bitů a také 32 bitů lze využít pro operace s hodnotami typu single/float a double:

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

Seznam popsaných instrukcí se nám úspěšně rozšiřuje:

Skupina Další dělení
Load-Store Load-Store pro jeden registr
  Load-Store pro dvojici registrů
  Prefetch
   
Skoky Nepodmíněné skoky
  Skoky do subrutiny
  Nepodmíněný skok na adresu v registru
  Podmíněné skoky
   
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  
  Bitové posuny
  Aritmetické posuny
  Podmíněné zpracování dat
  Podmíněný výběr operandu
  Další instrukce s podmínkou
  Extrakce dat
   
FP operace Přenos operandů mezi registry
  Načtení hodnoty do FP registru
  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

Poznámka: 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ů)

2. Načtení konstanty do FP registru

Pro načtení konstanty typu single/float a double se používá instrukce FMOV. Ovšem vzhledem k tomu, že jak instrukční slovo, tak i konstanta mají dohromady pouhých 32 bitů, 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:

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

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.

Tento ú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 použití. 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

3. Přenos operandů mezi registry

Další skupina instrukcí sice taktéž používá mnemotechnickou zkratku FMOV, ovšem neslouží k načtení konstanty, ale k přenosu operandu 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 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 ze 128 bitového registru Vd:

# Instrukce Stručný popis
1 FMOV Sd, Sn přenos mezi registry (single)
2 FMOV Wd, Sn přenos mezi registry (32bit integer, single)
3 FMOV Sd, Wn přenos mezi registry (32bit integer, single)
     
4 FMOV Dd, Dn přenos mezi registry (double)
5 FMOV Xd, Dn přenos mezi registry (integer, double)
6 FMOV Dd, Xn přenos mezi registry (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 nezmení

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

Pro konverzi hodnot mezi různými formáty (half float, single, double) slouží instrukce nazvaná FCVT (float convert). Některé převody lze provést bez problémů (neztratí se ani přesnost ani rozsah), u dalších převodů buď ztratíme přesnost nebo bude hodnota převedena na ∞ nebo -∞:

# 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)
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)
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)

Opět se podívejme na 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;
// 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]

5. Převod na celá čísla (zaokrouhlení)

Poměrně rozsáhlá skupina instrukcí slouží pro převod FP hodnot na celá čísla. Podívejme se na tabulku:

# Instrukce Stručný popis
1 FCVTAS konverze FP na integer se znaménkem, zaokrouhlení směrem k nekonečnům
2 FCVTAU dtto, ale konverze na unsigned integer
3 FCVTMS konverze na signed integer se zaokrouhlením směrem k -∞
4 FCVTMU konverze na unsigned integer se zaokrouhlením směrem k -∞
5 FCVTNS konverze se zaokrouhlením na nejbližší sudé číslo
6 FCVTNU dtto pro unsigned integer
7 FCVTPS konverze na signed integer se zaokrouhlením směrem k +∞
8 FCVTPU konverze na unsigned integer se zaokrouhlením směrem k +∞
9 FCVTZS konverze 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
12 UCVTF zpětná konverze na FP hodnotu

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 do assembleru:

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

6. Základní aritmetické operace

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

Opět se podívejme na příklad, tentokrát s poněkud 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]

7. Porovnání operandů

Instrukce, které slouží pro porovnání dvou FP registrů, nastavují příznakové bity N, V, Z a C. 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 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 na jednoduchý 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ů

8. SIMD operace

Pro SIMD operace, tj. operace pracující s krátkými vektory, se 128bitové registry Vn rozdělují takto:

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

U některých instrukcí se uvádí jak zdrojový a cílový vektor, tak i index prvku vektoru. Zápis v assembleru potom může vypadat například takto:

DUP V1.8B, V2.8B, [5]

Poznámka: počet „vektorových“ instrukcí je větší, než počet všech zbývajících instrukcí (včetně instrukcí matematického koprocesoru), takže si dnes budeme moci ukázat pouze malou skupinu vybraných operací. Navíc překladače většinou „vektorové“ instrukce přímo negenerují, pokud samozřejmě ve zdrojových kódech nepoužijeme příslušné intrinsic funkce.

9. Operace s prvky vektorů

Instrukce, s nimiž se seznámíme v této kapitole, slouží k různým manipulacím s prvky vektorů. Příkladem může být první instrukce DUP, která ve své nejjednodušší podobě dokáže zkopírovat vybraný prvek ze zdrojového vektoru do všech prvků vektoru cílového. Navíc může tato instrukce provádět i konverzi dat (například z bajtů na 16bitová celá čísla) atd.:

# Instrukce Stručný popis
1 DUP vybraný prvek zdrojového vektoru je rozkopírován do všech prvků vektoru cílového
2 DUP Vd.typ, Wn speciální případ pro rozkopírování n-bitů celočíselného registru
3 DUP Vd.typ, Xn speciální případ pro rozkopírování n-bitů celočíselného registru
     
4 INS Vd.typ[index], Vn.typ[index2] přenos jediného prvku ze zdrojového vektoru do vektoru cílového
5 INS Vd.typ, Wn přenos jediného prvku z celočíselného registru do cílového vektoru
6 INS Vd.typ, Xn přenos jediného prvku z celočíselného registru do cílového vektoru
     
7 MOV Vd.typ, Xn alias pro INS
8 MOV Vd.typ, Wn alias pro INS
9 MOV Vd.typ, Xn alias pro INS
     
10 UMOV Vd.typ, Wn přenos prvku z celočíselného registru, pokud dochází ke konverzi, je bezznaménková (doplnění nulami)
11 UMOV Vd.typ, Xn přenos prvku z celočíselného registru, pokud dochází ke konverzi, je bezznaménková (doplnění nulami)
     
12 SMOV Vd.typ, Wn přenos prvku z celočíselného registru, pokud dochází ke konverzi, je provedeno se znaménkem
13 SMOV Vd.typ, Xn přenos prvku z celočíselného registru, pokud dochází ke konverzi, je provedeno se znaménkem

10. Aritmetické operace s prvky uloženými ve vektorech

Skupina aritmetických instrukcí s prvky uloženými ve vektorech je poměrně rozsáhlá, protože kromě základních instrukcí pro součet, rozdíl, součin a podíl jsou implementovány instrukce pro výpočet rozdílu s následným uložením absolutní hodnoty rozdílu nebo pro součet/rozdíl a vydělení výsledku dvěma. Většina instrukcí pracuje se všemi typy prvků, tj. s vektory zmíněnými v osmé kapitole:

# Instrukce Stručný popis
1 ADD Vd.typ, Vn.typ, Vm.typ součet prvek po prvku (libovolný celočíselný typ)
2 FADD Vd.typ, Vn.typ, Vm.typ součet prvek po prvku (2×single, 4×single, 2×double)
3 SUB Vd.typ, Vn.typ, Vm.typ rozdíl prvek po prvku (libovolný celočíselný typ)
4 FSUB Vd.typ, Vn.typ, Vm.typ rozdíl prvek po prvku (2×single, 4×single, 2×double)
5 MUL Vd.typ, Vn.typ, Vm.typ součin prvek po prvku (libovolný celočíselný typ)
6 FMUL Vd.typ, Vn.typ, Vm.typ součin prvek po prvku (2×single, 4×single, 2×double)
7 FDIV Vd.typ, Vn.typ, Vm.typ podíl prvek po prvku (2×single, 4×single, 2×double)
     
8 UABD Vd.typ, Vn.typ, Vm.typ rozdíl prvek po prvku, do výsledného vektoru se uloží absolutní hodnota rozdílu
9 SABD Vd.typ, Vn.typ, Vm.typ dtto, ale pro celočíselný typ se znaménkem
10 UABA Vd.typ, Vn.typ, Vm.typ obdoba UABD, ale výsledek (abs.rozdíly) se akumulují
11 SABA Vd.typ, Vn.typ, Vm.typ dtto, ale pro celočíselný typ se znaménkem
12 FABD Vd.typ, Vn.typ, Vm.typ rozdíl prvek po prvku, do výsledného vektoru se uloží absolutní hodnota rozdílu
     
13 UHADD Vd.typ, Vn.typ, Vm.typ součet a následné vydělení dvěma (či bitový posun)
14 SHADD Vd.typ, Vn.typ, Vm.typ dtto, ale pro celočíselný typ se znaménkem
15 UHSUB Vd.typ, Vn.typ, Vm.typ rozdíl a následné vydělení dvěma (či bitový posun)
16 SHSUB Vd.typ, Vn.typ, Vm.typ dtto, ale pro celočíselný typ se znaménkem

11. Doplnění – problematika načítání konstant

Již několikrát jsme se zmínili o tom, že kvůli konstantní šířce všech instrukcí může být problematické uložení konstanty do některého pracovního registru. Problém je to logický a vlastně shodný pro prakticky všechny „klasické“ RISCové mikroprocesory: šířka pracovních registrů je 32 bitů a současně je šířka instrukcí taktéž 32 bitů, tudíž není možné, aby se v instrukci vedle operačního kódu nacházela i 32 bitová konstanta. Tvůrci dalších RISCových mikroprocesorů se s touto problematikou snažili vypořádat různým způsobem, například zavedli speciální instrukci pro naplnění horních šestnácti bitů registru, zatímco pro naplnění spodních šestnácti bitů bylo možné použít například instrukci ADD s konstantou a nulovým registrem R0 (zhruba takovýmto způsobem je tato problematika řešena na mikroprocesorech MIPS).

U procesorů AArch64 je možné pro načtení konstanty použít kombinace různých instrukcí, typicky MOV a MOVK nebo MOVN a MOVK. V praxi může překladač postupovat následovně:

int a = 100;
int b = 1000;
int c = 10000;
int d = 100000;
int e = 1000000;
int f = 10000000;

Tato inicializace lokálních proměnných se přeloží takto:

// int a = 100;
mov     w0, 100
str     w0, [sp, 28]
 
// int b = 1000;
mov     w0, 1000
str     w0, [sp, 24]
 
// int c = 10000;
mov     w0, 10000
str     w0, [sp, 20]
 
// int d = 100000;
mov     w0, 34464
movk    w0, 0x1, lsl 16
str     w0, [sp, 16]
 
// int e = 1000000;
mov     w0, 16960
movk    w0, 0xf, lsl 16
str     w0, [sp, 12]
 
// int f = 10000000;
mov     w0, 38528
movk    w0, 0x98, lsl 16
str     w0, [sp, 8]

Povšimněte si, že u instrukce MOVK se určuje, do kterého místa cílového registru se konstanta načte (jinými slovy jak se konstanta posune doleva).

12. Volání služeb jádra (syscall) na procesorech AArch64

Volání služeb jádra Linuxu se na procesorech AArch64 hned v několika ohledech odlišuje od původního způsobu volání, které známe z procesorů ARM (dnes tedy ARM32). První změna je pochopitelná – při předávání celočíselných argumentů se používají 64bitové registry x0x30 a nikoli 32bitové registry r0r14. Druhá změna spočívá v tom, že samotné číslo syscallu se ukládá do registru x8 a nikoli do registru r7. Tuto změnu si můžete poměrně snadno zapamatovat pomocí mnemotechnické pomůcky, protože nejnovější 32bitová jádra mají architekturu ARMv7-A zatímco 64bitová jádra ARMv8-A. Kupodivu došlo i ke změně čísel syscallů, které jsou od původní 32bitové architektury zcela odlišné. Nová čísla syscallů můžete najít například přímo v hlavičkovém souboru Linuxu na adrese https://github.com/torval­ds/linux/blob/master/inclu­de/uapi/asm-generic/unistd.h.

13. Příklad programu volajícího službu jádra exit

Pro zajímavost se podívejme, jak by vypadal program (pravděpodobně dokonce nejjednodušší program vůbec), který po svém spuštění pouze zavolá službu jádra, která program ukončí a předá volajícímu (rodičovskému) procesu návratový kód. Celý program obsahuje pouze tři instrukce, přičemž první instrukce slouží pro naplnění registru obsahujícího číslo služby jádra, druhá instrukce naplní registr s návratovým kódem a třetí instrukce skutečně zavolá jádro (svc znamená supervisor call). V GNU assembleru by varianta určená pro 32bitové procesory ARM vypadala následovně:

# asmsyntax=as
 
# Linux kernel system call
sys_exit=1
 
 
 
.section .data
 
.section .bss
 
.section .text
        .global _start          @ tento symbol ma byt dostupny i z linkeru
 
_start:
        mov   r7,$sys_exit      @ cislo sycallu pro funkci "exit"
        mov   r0,#0             @ exit code = 0
        svc   0                 @ volani Linuxoveho kernelu

Naproti tomu verze pro 64bitové procesory AArch 64 vypadá nepatrně odlišně:

# asmsyntax=as
 
# Linux kernel system call
sys_exit=93
 
 
 
.section .data
 
.section .bss
 
.section .text
        .global _start          // tento symbol ma byt dostupny i z linkeru
 
_start:
        mov  x8, #sys_exit      // cislo sycallu pro funkci "exit"
        mov  x0, #0             // exit code = 0
        svc  0                  // volani Linuxoveho kernelu

Můžeme vidět, že změny se týkají použitých registrů a čísla syscallu. Po překladu však získáme zcela odlišný binární soubor, protože se kódování instrukcí mezi ARM32 a AArch64 zcela změnilo.

Překlad příkladu a jeho slinkování se provede standardním způsobem, tedy s použitím nástrojů as (GNU Assembler – GAS) a ld (GNU Linker):

CS24_early

as aarch64.s -o aarch64.o
ld -s aarch64.o

14. Výsledná podoba binárního souboru s přeloženými instrukcemi

Pro zajímavost se podívejme na obsah vytvořeného spustitelného binárního souboru a.out. Tento obsah získáme dalším nástrojem nazvaným objdump:

objdump -f -d -t -h a.out
a.out:     file format elf64-littleaarch64
architecture: aarch64, flags 0x00000102:
EXEC_P, D_PAGED
start address 0x0000000000400078
 
Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         0000000c  0000000000400078  0000000000400078  00000078  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
SYMBOL TABLE:
no symbols
 
 
 
Disassembly of section .text:
 
0000000000400078 <.text>:
  400078:       d2800ba8        mov     x8, #0x5d                       // #93
  40007c:       d2800000        mov     x0, #0x0                        // #0
  400080:       d4000001        svc     #0x0

Povšimněte si, že všechny instrukce mají skutečně konstantní šířku 32 bitů. Navíc je zajímavé, že obě instrukce mov pracují se skutečnými konstantami uloženými přímo v instrukčním slovu (což je ovšem možné jen pro určité konstanty, typicky pro malá kladná i záporná čísla).

15. Odkazy na Internetu

  1. GNU Assembler Examples
    http://cs.lmu.edu/~ray/no­tes/gasexamples/
  2. GNU Binutils
    https://www.gnu.org/softwa­re/binutils/
  3. GNU Assembler
    https://en.wikipedia.org/wi­ki/GNU_Assembler
  4. Exploring AArch64 assembler – Chapter 1
    http://thinkingeek.com/2016/10/08/ex­ploring-aarch64-assembler-chapter1/
  5. Exploring AArch64 assembler – Chapter 2
    http://thinkingeek.com/2016/10/08/ex­ploring-aarch64-assembler-chapter-2/
  6. Aarch64 Register and Instruction Quick Start
    https://wiki.cdot.senecaco­llege.ca/wiki/Aarch64_Regis­ter_and_Instruction_Quick_Start
  7. Comparison of ARMv8-A cores
    https://en.wikipedia.org/wi­ki/Comparison_of_ARMv8-A_cores
  8. A64 General Instructions
    http://www.keil.com/suppor­t/man/docs/armclang_asm/ar­mclang_asm_pge1427898258836­.htm
  9. ARMv8 (AArch64) Instruction Encoding
    http://kitoslab-eng.blogspot.cz/2012/10/armv8-aarch64-instruction-encoding.html
  10. Cortex-A32 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a32-processor.php
  11. Cortex-A35 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a35-processor.php
  12. Cortex-A53 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a53-processor.php
  13. Cortex-A57 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a57-processor.php
  14. Cortex-A72 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a72-processor.php
  15. Cortex-A73 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a73-processor.php
  16. Apple A7 (SoC založen na CPU Cyclone)
    https://en.wikipedia.org/wi­ki/Apple_A7
  17. System cally pro AArch64 na Linuxu
    https://github.com/torval­ds/linux/blob/master/inclu­de/uapi/asm-generic/unistd.h
  18. Architectures/AArch64 (FedoraProject.org)
    https://fedoraproject.org/wi­ki/Architectures/AArch64
  19. SIG pro AArch64 (CentOS)
    https://wiki.centos.org/Spe­cialInterestGroup/AltArch/A­Arch64
  20. The ARMv8 instruction sets
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.den0024a/ch05s01.html
  21. A64 Instruction Set
    https://developer.arm.com/pro­ducts/architecture/instruc­tion-sets/a64-instruction-set
  22. Switching between the instruction sets
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.den0024a/ch05s01.html
  23. The A64 instruction set
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.den0024a/ch05s01.html
  24. Introduction to ARMv8 64-bit Architecture
    https://quequero.org/2014/04/in­troduction-to-arm-architecture/
  25. MCU market turns to 32-bits and ARM
    http://www.eetimes.com/do­cument.asp?doc_id=1280803
  26. Cortex-M0 Processor (ARM Holdings)
    http://www.arm.com/produc­ts/processors/cortex-m/cortex-m0.php
  27. Cortex-M0+ Processor (ARM Holdings)
    http://www.arm.com/produc­ts/processors/cortex-m/cortex-m0plus.php
  28. ARM Processors in a Mixed Signal World
    http://www.eeweb.com/blog/arm/arm-processors-in-a-mixed-signal-world
  29. ARM Architecture (Wikipedia)
    https://en.wikipedia.org/wi­ki/ARM_architecture
  30. DSP for Cortex-M
    https://developer.arm.com/techno­logies/dsp/dsp-for-cortex-m
  31. 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
  32. 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
  33. Q (number format)
    https://en.wikipedia.org/wi­ki/Q_%28number_format%29
  34. 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
  35. TriCoreTM V1.6 Instruction Set: 32-bit Unified Processor Core
    http://www.infineon.com/dgdl/tc_v131_in­structionset_v138.pdf?file­Id=db3a304412b407950112b409b6dd0352
  36. TriCore v2.2 C Compiler, Assembler, Linker Reference Manual
    http://tasking.com/suppor­t/tricore/tc_reference_gu­ide_v2.2.pdf
  37. Infineon TriCore (Wikipedia)
    https://en.wikipedia.org/wi­ki/Infineon_TriCore
  38. 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
  39. Comparing four 32-bit soft processor cores
    http://www.eetimes.com/au­thor.asp?section_id=14&doc_id=1286116
  40. RISC-V Instruction Set
    http://riscv.org/download­.html#spec_compressed_isa
  41. RISC-V Spike (ISA Simulator)
    http://riscv.org/download.html#isa-sim
  42. RISC-V (Wikipedia)
    https://en.wikipedia.org/wiki/RISC-V
  43. David Patterson (Wikipedia)
    https://en.wikipedia.org/wi­ki/David_Patterson_(compu­ter_scientist)
  44. OpenRISC (oficiální stránky projektu)
    http://openrisc.io/
  45. OpenRISC architecture
    http://openrisc.io/architecture.html
  46. Emulátor OpenRISC CPU v JavaScriptu
    http://s-macke.github.io/jor1k/demos/main.html
  47. OpenRISC (Wikipedia)
    https://en.wikipedia.org/wi­ki/OpenRISC
  48. OpenRISC – instrukce
    http://sourceware.org/cgen/gen-doc/openrisc-insn.html
  49. OpenRISC – slajdy z přednášky o projektu
    https://iis.ee.ethz.ch/~gmichi/a­socd/lecturenotes/Lecture6­.pdf
  50. Berkeley RISC
    http://en.wikipedia.org/wi­ki/Berkeley_RISC
  51. Great moments in microprocessor history
    http://www.ibm.com/develo­perworks/library/pa-microhist.html
  52. Microprogram-Based Processors
    http://research.microsoft.com/en-us/um/people/gbell/Computer_Struc­tures_Principles_and_Exam­ples/csp0167.htm
  53. Great Microprocessors of the Past and Present
    http://www.cpushack.com/CPU/cpu1.html
  54. A Brief History of Microprogramming
    http://www.cs.clemson.edu/~mar­k/uprog.html
  55. What is RISC?
    http://www-cs-faculty.stanford.edu/~ero­berts/courses/soco/projec­ts/2000–01/risc/whatis/
  56. RISC vs. CISC
    http://www-cs-faculty.stanford.edu/~ero­berts/courses/soco/projec­ts/2000–01/risc/risccisc/
  57. RISC and CISC definitions:
    http://www.cpushack.com/CPU/cpu­AppendA.html
  58. FPGA
    https://cs.wikipedia.org/wi­ki/Programovateln%C3%A9_hra­dlov%C3%A9_pole
  59. The Evolution of RISC
    http://www.ibm.com/develo­perworks/library/pa-microhist.html#sidebar1

Byl pro vás článek přínosný?

Autor článku

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