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
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
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 v0 až v31 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 x0 až x30 a nikoli 32bitové registry r0 až r14. 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/torvalds/linux/blob/master/include/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):
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
- GNU Assembler Examples
http://cs.lmu.edu/~ray/notes/gasexamples/ - GNU Binutils
https://www.gnu.org/software/binutils/ - GNU Assembler
https://en.wikipedia.org/wiki/GNU_Assembler - Exploring AArch64 assembler – Chapter 1
http://thinkingeek.com/2016/10/08/exploring-aarch64-assembler-chapter1/ - Exploring AArch64 assembler – Chapter 2
http://thinkingeek.com/2016/10/08/exploring-aarch64-assembler-chapter-2/ - Aarch64 Register and Instruction Quick Start
https://wiki.cdot.senecacollege.ca/wiki/Aarch64_Register_and_Instruction_Quick_Start - Comparison of ARMv8-A cores
https://en.wikipedia.org/wiki/Comparison_of_ARMv8-A_cores - A64 General Instructions
http://www.keil.com/support/man/docs/armclang_asm/armclang_asm_pge1427898258836.htm - ARMv8 (AArch64) Instruction Encoding
http://kitoslab-eng.blogspot.cz/2012/10/armv8-aarch64-instruction-encoding.html - Cortex-A32 Processor
https://www.arm.com/products/processors/cortex-a/cortex-a32-processor.php - Cortex-A35 Processor
https://www.arm.com/products/processors/cortex-a/cortex-a35-processor.php - Cortex-A53 Processor
https://www.arm.com/products/processors/cortex-a/cortex-a53-processor.php - Cortex-A57 Processor
https://www.arm.com/products/processors/cortex-a/cortex-a57-processor.php - Cortex-A72 Processor
https://www.arm.com/products/processors/cortex-a/cortex-a72-processor.php - Cortex-A73 Processor
https://www.arm.com/products/processors/cortex-a/cortex-a73-processor.php - Apple A7 (SoC založen na CPU Cyclone)
https://en.wikipedia.org/wiki/Apple_A7 - System cally pro AArch64 na Linuxu
https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h - Architectures/AArch64 (FedoraProject.org)
https://fedoraproject.org/wiki/Architectures/AArch64 - SIG pro AArch64 (CentOS)
https://wiki.centos.org/SpecialInterestGroup/AltArch/AArch64 - The ARMv8 instruction sets
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch05s01.html - A64 Instruction Set
https://developer.arm.com/products/architecture/instruction-sets/a64-instruction-set - Switching between the instruction sets
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch05s01.html - The A64 instruction set
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch05s01.html - Introduction to ARMv8 64-bit Architecture
https://quequero.org/2014/04/introduction-to-arm-architecture/ - MCU market turns to 32-bits and ARM
http://www.eetimes.com/document.asp?doc_id=1280803 - Cortex-M0 Processor (ARM Holdings)
http://www.arm.com/products/processors/cortex-m/cortex-m0.php - Cortex-M0+ Processor (ARM Holdings)
http://www.arm.com/products/processors/cortex-m/cortex-m0plus.php - ARM Processors in a Mixed Signal World
http://www.eeweb.com/blog/arm/arm-processors-in-a-mixed-signal-world - ARM Architecture (Wikipedia)
https://en.wikipedia.org/wiki/ARM_architecture - DSP for Cortex-M
https://developer.arm.com/technologies/dsp/dsp-for-cortex-m - Cortex-M processors in DSP applications? Why not?!
https://community.arm.com/processors/b/blog/posts/cortex-m-processors-in-dsp-applications-why-not - White Paper – DSP capabilities of Cortex-M4 and Cortex-M7
https://community.arm.com/processors/b/blog/posts/white-paper-dsp-capabilities-of-cortex-m4-and-cortex-m7 - Q (number format)
https://en.wikipedia.org/wiki/Q_%28number_format%29 - TriCore Architecture & Core
http://www.infineon.com/cms/en/product/microcontroller/32-bit-tricore-tm-microcontroller/tricore-tm-architecture-and-core/channel.html?channel=ff80808112ab681d0112ab6b73d40837 - TriCoreTM V1.6 Instruction Set: 32-bit Unified Processor Core
http://www.infineon.com/dgdl/tc_v131_instructionset_v138.pdf?fileId=db3a304412b407950112b409b6dd0352 - TriCore v2.2 C Compiler, Assembler, Linker Reference Manual
http://tasking.com/support/tricore/tc_reference_guide_v2.2.pdf - Infineon TriCore (Wikipedia)
https://en.wikipedia.org/wiki/Infineon_TriCore - C166®S V2 Architecture & Core
http://www.infineon.com/cms/en/product/microcontroller/16-bit-c166-microcontroller/c166-s-v2-architecture-and-core/channel.html?channel=db3a304312bef5660112c3011c7d01ae - Comparing four 32-bit soft processor cores
http://www.eetimes.com/author.asp?section_id=14&doc_id=1286116 - RISC-V Instruction Set
http://riscv.org/download.html#spec_compressed_isa - RISC-V Spike (ISA Simulator)
http://riscv.org/download.html#isa-sim - RISC-V (Wikipedia)
https://en.wikipedia.org/wiki/RISC-V - David Patterson (Wikipedia)
https://en.wikipedia.org/wiki/David_Patterson_(computer_scientist) - OpenRISC (oficiální stránky projektu)
http://openrisc.io/ - OpenRISC architecture
http://openrisc.io/architecture.html - Emulátor OpenRISC CPU v JavaScriptu
http://s-macke.github.io/jor1k/demos/main.html - OpenRISC (Wikipedia)
https://en.wikipedia.org/wiki/OpenRISC - OpenRISC – instrukce
http://sourceware.org/cgen/gen-doc/openrisc-insn.html - OpenRISC – slajdy z přednášky o projektu
https://iis.ee.ethz.ch/~gmichi/asocd/lecturenotes/Lecture6.pdf - Berkeley RISC
http://en.wikipedia.org/wiki/Berkeley_RISC - Great moments in microprocessor history
http://www.ibm.com/developerworks/library/pa-microhist.html - Microprogram-Based Processors
http://research.microsoft.com/en-us/um/people/gbell/Computer_Structures_Principles_and_Examples/csp0167.htm - Great Microprocessors of the Past and Present
http://www.cpushack.com/CPU/cpu1.html - A Brief History of Microprogramming
http://www.cs.clemson.edu/~mark/uprog.html - What is RISC?
http://www-cs-faculty.stanford.edu/~eroberts/courses/soco/projects/2000–01/risc/whatis/ - RISC vs. CISC
http://www-cs-faculty.stanford.edu/~eroberts/courses/soco/projects/2000–01/risc/risccisc/ - RISC and CISC definitions:
http://www.cpushack.com/CPU/cpuAppendA.html - FPGA
https://cs.wikipedia.org/wiki/Programovateln%C3%A9_hradlov%C3%A9_pole - The Evolution of RISC
http://www.ibm.com/developerworks/library/pa-microhist.html#sidebar1