V dnešní části seriálu o použití assembleru v Linuxu si na několika demonstračních příkladech ukážeme, jakým způsobem je možné využívat nepodmíněné i podmíněné skoky na procesorech s architekturou AArch64 při implementaci programových smyček s podmínkou testovanou na začátku či na konci každé iterace. Uvidíme, že se instrukční sady ARM32 a AArch64 v tomto ohledu od sebe poněkud odlišují.

Obsah

1. Použití assembleru v Linuxu: RISCová architektura AArch64 (pokračování)

2. Od podmínkových bitů k podmíněným skokům

3. Základní podmíněné skoky na architektuře AArch64

4. Počítaná programová smyčka – první varianta

5. Porovnání verze pro ARM32 s verzí pro AArch64

6. Počítaná programová smyčka – druhá varianta

7. Porovnání verze pro ARM32 s verzí pro AArch64

8. Test na ukončení smyčky na začátku každé iterace

9. První varianta smyčky s testem na začátku

10. Porovnání s ARM32

11. Druhá varianta smyčky s testem na začátku

12. Porovnání s ARM32

13. Instrukce CBZ a CBNZ

14. Úprava prvního demonstračního příkladu použitím instrukce CBNZ

15. Odkazy na Internetu

1. Použití assembleru v Linuxu: RISCová architektura AArch64 (pokračování)

Jedním z důležitých rozdílů mezi 32bitovou RISCovou architekturou ARM a 64bitovou architekturou AArch64 je absence podmínkových bitů u AArch64. To mj. znamená, že se v programovém kódu bude nacházet větší množství skoků, protože není možné využít vynechání či přeskočení (skip) nějaké instrukce či sekvence instrukcí v závislosti na aktuálním nastavení bitových příznaků (zero, carry, overflow, negative). Na druhou stranu však v instrukční sadě AArch64 nalezneme některé nové užitečné a poměrně často používané instrukce, například podmíněné skoky CBZ a CBNZ, které byly již dříve přidány do instrukční sady Thumb-2 (ovšem v původní RISCové instrukční sadě je nenajdeme). V dnešním článku si nejprve ukážeme, jakým způsobem se musí přepsat programy používající počítané programové smyčky s testem na začátku či na konci a jak lze v těchto smyčkách využít zmíněné instrukce CBZ a CBNZ. Zmíníme se i o některých dalších omezeních, s nimiž je nutné na AArch64 počítat; například s nutností použití 32bitového registru Wx při zápisu jednotlivých bajtů (znaků) do operační paměti.

2. Od podmínkových bitů k podmíněným skokům

Již v předchozím článku jsme si řekli, že instrukční sada AArch64 se od původní 32bitové RISCové instrukční sady ARM32 (či zkráceně pouze A32) odlišuje mj. i v tom, že se zredukoval počet instrukcí, u nichž je možné použít podmínkové bity. Jen ve stručnosti si připomeňme, že v instrukční sadě ARM32 jsou v prakticky každém instrukčním slovu (každé má konstantní šířku třiceti dvou bitů) rezervovány čtyři nejvyšší bity, v nichž je zapsán kód podmínky, při jejímž splnění se instrukce provede. Díky této vlastnosti bylo možné v mnoha algoritmech zredukovat počet podmíněných skoků, což je v případě RISCových procesorů poměrně důležité.

Všechny podmínky jsou vyhodnoceny na základě hodnoty jednoho či (častěji) většího množství příznaků. První sada podmínkových kódů se používá pro provedení či naopak neprovedení instrukce na základě hodnoty jednoho z příznakových bitů Z (zero), V (overflow) či N (negative). Poslední podmínkový kód z této skupiny má název AL (Any/Always) a značí, že se instrukce provede v každém případě. Tento podmínkový kód se tudíž většinou v assembleru ani nezapisuje, protože je považován za implicitní:

Kód Přípona Význam Testovaná podmínka
0000 EQ Z = 1 rovnost (či nulový výsledek)
0001 NE Z = 0 nerovnost (či nenulový výsledek)
0100 MI N = 1 výsledek je záporný
0101 PL N = 0 výsledek je kladný či 0
0110 VS V = 1 nastalo přetečení
0111 VC V = 0 nenastalo přetečení
1110 AL Any/Always většinou se nezapisuje, implicitní podmínka

Další čtyři podmínkové kódy se většinou používají při porovnávání dvou hodnot bez znaménka (unsigned). V těchto případech se testují stavy příznakových bitů C (carry) a Z (zero), přesněji řečeno kombinace těchto bitů:

Kód Přípona Význam Testovaná podmínka
0010 CS/HS C = 1
0011 CC/LO C = 0 <
1000 HI C = 1 & Z = 0 >
1001 LS C = 0 | Z = 1

Poslední čtyři podmínkové kódy se používají pro porovnávání hodnot se znaménkem (signed). V těchto případech se namísto příznakových bitů (C) carry a (Z) zero testují kombinace bitů (N) negative, (V) overflow a (Z) zero:

Kód Přípona Význam Testovaná podmínka
1010 GE N == V
1011 LT N ≠ V <
1100 GT Z = 0, N = V >
1101 LE Z = 1, N ≠ V

3. Základní podmíněné skoky na architektuře AArch64

Procesory s architekturou AArch64 sice používají shodné podmínkové bity, ty jsou ovšem použity jen v několika instrukcích. Příznak přetečení je, podobně jako u mnoha dalších typů procesorů, používán při aritmetických operacích a testy podmínkových bitů lze provádět především u podmíněných skoků, tj. u instrukcí, jejichž mnemotechnická zkratka začíná znakem „B“ od slova „Branch“. Rozeznáváme následující typy nepodmíněných podmíněných skoků:

# Instrukce Alternativní zápis
1 B BAL
2 B.EQ BEQ
3 B.NE BNE
4 B.MI BMI
5 B.PL BPL
6 B.VS BVS
7 B.VC BVC
8 B.CS BCS
9 B.CC BCC
10 B.HI BHI
11 B.LS BLS
12 B.GE BGE
13 B.LT BLT
14 B.GT BGT
15 B.LE BLE

Poznámka: alternativní zápis je podporován například GNU Assemblerem, který budeme používat v demonstračních příkladech.

4. Počítaná programová smyčka – první varianta

Nastavení příznakových bitů a podmíněné skoky si otestujeme na demonstračním příkladu, s nímž jsme se již seznámili na jiných architekturách. V tomto příkladu je implementována počítaná programová smyčka, v níž se naplňuje řetězec (resp. přesněji řečeno předem zvolená oblast paměti) znakem „*“. První varianta tohoto příkladu vypadá na architektuře AArch64 prakticky stejně, jako tomu bylo u dalších popisovaných typů mikroprocesorů, tj. x86, x86-64 i ARM32 – používá se sekvence tří instrukcí určených pro snížení hodnoty počitadla smyčky o jedničku, testu, zda již počitadlo dosáhlo nuly a podmíněného skoku provedeného za předpokladu, že se nuly nedosáhlo:

loop:
        strb  w3, [x1]               // zapis znaku do bufferu
        add   x1, x1, #1             // uprava ukazatele do bufferu
        sub   x2, x2, #1             // zmenseni pocitadla
        cmp   x2, #0                 // otestovani, zda jsme jiz nedosahli nuly
        bne   loop                   // pokud jsme se nedostali k nule, skok na zacatek smycky

Povšimněte si, že při zápisu bajtu (znaku) do operační paměti instrukcí strb (store byte) je nutné použít 32bitový registr w3 a nikoli 64bitový registrx3. Do paměti se zapisuje samozřejmě pouze spodních osm bitů.

Úplný zdrojový kód tohoto příkladu vypadá následovně:

# asmsyntax=as
 
# Testovaci program naprogramovany v assembleru GNU as
# - pocitana programova smycka
# - uprava pro mikroprocesory s architekturou AArch64
#
# Autor: Pavel Tisnovsky
 
 
 
# Linux kernel system call table
sys_exit   = 93
sys_write  = 64

# List of syscalls for AArch64:
# https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h
 
# Dalsi konstanty pouzite v programu - standardni streamy
std_input  = 0
std_output = 1

# pocet opakovani znaku
rep_count  = 40
 
 
 
#-----------------------------------------------------------------------------
.section .data
 
 
 
#-----------------------------------------------------------------------------
.section .bss
        .lcomm buffer, rep_count     // rezervace bufferu pro vystup
 
 
 
#-----------------------------------------------------------------------------
.section .text
        .global _start               // tento symbol ma byt dostupny i linkeru
 
_start:
        ldr   x1, =buffer            // zapis se bude provadet do tohoto bufferu
        mov   x2, #rep_count         // pocet opakovani znaku
        mov   w3, #'*'               // zapisovany znak
loop:
        strb  w3, [x1]               // zapis znaku do bufferu
        add   x1, x1, #1             // uprava ukazatele do bufferu
        sub   x2, x2, #1             // zmenseni pocitadla
        cmp   x2, #0                 // otestovani, zda jsme jiz nedosahli nuly
        bne   loop                   // pokud jsme se nedostali k nule, skok na zacatek smycky
 
        mov   x8, #sys_write         // cislo syscallu pro funkci "write"
        mov   x0, #std_output        // standardni vystup
        ldr   x1, =buffer            // adresa retezce, ktery se ma vytisknout
        mov   x2, #rep_count         // pocet znaku, ktere se maji vytisknout
        svc   0                      // volani Linuxoveho kernelu
 
        mov   x8, #sys_exit          // cislo sycallu pro funkci "exit"
        mov   x0, #0                 // exit code = 0
        svc   0                      // volani Linuxoveho kernelu

Překlad provedeme nám již známým způsobem (zde se jednotlivé architektury od sebe prakticky neodlišují):

as loop1-aarch64-v1.s -o loop1-aarch64-v1.o
ld -s loop1-aarch64-v1.o

Zpětný překlad lze získat klasickým „disassemblingem“:

objdump -f -d -t -h a.out

Výstup z disassembleru bude vypadat následovně:

a.out:     file format elf64-littleaarch64
architecture: aarch64, flags 0x00000102:
EXEC_P, D_PAGED
start address 0x00000000004000b0

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000048  00000000004000b0  00000000004000b0  000000b0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .bss          00000028  00000000004100f8  00000000004100f8  000000f8  2**3
                  ALLOC
SYMBOL TABLE:
no symbols



Disassembly of section .text:

00000000004000b0 <.text>:
  4000b0:       58000201        ldr     x1, 0x4000f0
  4000b4:       d2800502        mov     x2, #0x28                       // #40
  4000b8:       52800543        mov     w3, #0x2a                       // #42
  4000bc:       39000023        strb    w3, [x1]
  4000c0:       91000421        add     x1, x1, #0x1
  4000c4:       d1000442        sub     x2, x2, #0x1
  4000c8:       f100005f        cmp     x2, #0x0
  4000cc:       54ffff81        b.ne    0x4000bc
  4000d0:       d2800808        mov     x8, #0x40                       // #64
  4000d4:       d2800020        mov     x0, #0x1                        // #1
  4000d8:       580000c1        ldr     x1, 0x4000f0
  4000dc:       d2800502        mov     x2, #0x28                       // #40
  4000e0:       d4000001        svc     #0x0
  4000e4:       d2800ba8        mov     x8, #0x5d                       // #93
  4000e8:       d2800000        mov     x0, #0x0                        // #0
  4000ec:       d4000001        svc     #0x0
  4000f0:       004100f8        .inst   0x004100f8 ; undefined
  4000f4:       00000000        .inst   0x00000000 ; undefined

Povšimněte si, že se ve zpětném překladu používá „ARMovský“ zápis instrukce podmíněného skoku: B.NE namísto alternativního zápisu BNE, který jsme použili ve zdrojovém kódu.

5. Porovnání verze pro ARM32 s verzí pro AArch64

Při porovnání dvou variant tohoto příkladu (ARM32 versus AArch64) vidíme, že zdrojové kódy jsou prakticky totožné a liší se od sebe pouze použitou syntaxí (odlišný zápis poznámek), jmény registrů (ve druhé verzi jsou použity 64bitové registry), a explicitním použitím 32bitového registru pro uložení bajtu (kódu znaku) do paměti:

Poznámka: povšimněte si rozdílných čísel syscallů, což je téma, kterému jsme se věnovali minule.

6. Počítaná programová smyčka – druhá varianta

Alternativní způsob zápisu smyčky spočívá v odstranění explicitního testování nulové hodnoty počitadla instrukcí CMP. Můžeme totiž využít toho, že se příznak Z (zero) může nastavit automaticky již při dekrementaci hodnoty počitadla. Na procesorech ARM je v tomto případě nutné namísto instrukce SUB použít instrukci SUBS, kde poslední znak „S“ znamená „set (flags)“. Celá programová smyčka se nám zkrátí o jednu instrukci, což může znamenat poměrně znatelné urychlení (samozřejmě nikoli v našem jednoduchém příkladu, ale například při výpočtech nad velkými poli se tato optimalizace již může projevit):

loop:
        strb  w3, [x1]               // zapis znaku do bufferu
        add   x1, x1, #1             // uprava ukazatele do bufferu
        subs  x2, x2, #1             // zmenseni pocitadla a soucasne nastaveni priznaku
        bne   loop                   // pokud jsme se nedostali k nule, skok na zacatek smycky

Následuje úplný zdrojový kód druhého demonstračního příkladu:

# asmsyntax=as
 
# Testovaci program naprogramovany v assembleru GNU as
# - pocitana programova smycka
# - uprava pro mikroprocesory s architekturou AArch64
#
# Autor: Pavel Tisnovsky
 
 
 
# Linux kernel system call table
sys_exit   = 93
sys_write  = 64

# List of syscalls for AArch64:
# https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h
 
# Dalsi konstanty pouzite v programu - standardni streamy
std_input  = 0
std_output = 1

# pocet opakovani znaku
rep_count  = 40
 
 
 
#-----------------------------------------------------------------------------
.section .data
 
 
 
#-----------------------------------------------------------------------------
.section .bss
        .lcomm buffer, rep_count     // rezervace bufferu pro vystup
 
 
 
#-----------------------------------------------------------------------------
.section .text
        .global _start               // tento symbol ma byt dostupny i linkeru
 
_start:
        ldr   x1, =buffer            // zapis se bude provadet do tohoto bufferu
        mov   x2, #rep_count         // pocet opakovani znaku
        mov   w3, #'*'               // zapisovany znak
loop:
        strb  w3, [x1]               // zapis znaku do bufferu
        add   x1, x1, #1             // uprava ukazatele do bufferu
        subs  x2, x2, #1             // zmenseni pocitadla a soucasne nastaveni priznaku
        bne   loop                   // pokud jsme se nedostali k nule, skok na zacatek smycky
 
        mov   x8, #sys_write         // cislo syscallu pro funkci "write"
        mov   x0, #std_output        // standardni vystup
        ldr   x1, =buffer            // adresa retezce, ktery se ma vytisknout
        mov   x2, #rep_count         // pocet znaku, ktere se maji vytisknout
        svc   0                      // volani Linuxoveho kernelu
 
        mov   x8, #sys_exit          // cislo sycallu pro funkci "exit"
        mov   x0, #0                 // exit code = 0
        svc   0                      // volani Linuxoveho kernelu

Výsledek zpětného překladu (disassemblingu) je následující:


a.out:     file format elf64-littleaarch64
architecture: aarch64, flags 0x00000102:
EXEC_P, D_PAGED
start address 0x00000000004000b0

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000048  00000000004000b0  00000000004000b0  000000b0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .bss          00000028  00000000004100f8  00000000004100f8  000000f8  2**3
                  ALLOC
SYMBOL TABLE:
no symbols



Disassembly of section .text:

00000000004000b0 <.text>:
  4000b0:       58000201        ldr     x1, 0x4000f0
  4000b4:       d2800502        mov     x2, #0x28                       // #40
  4000b8:       52800543        mov     w3, #0x2a                       // #42
  4000bc:       39000023        strb    w3, [x1]
  4000c0:       91000421        add     x1, x1, #0x1
  4000c4:       f1000442        subs    x2, x2, #0x1
  4000c8:       54ffffa1        b.ne    0x4000bc
  4000cc:       d2800808        mov     x8, #0x40                       // #64
  4000d0:       d2800020        mov     x0, #0x1                        // #1
  4000d4:       580000e1        ldr     x1, 0x4000f0
  4000d8:       d2800502        mov     x2, #0x28                       // #40
  4000dc:       d4000001        svc     #0x0
  4000e0:       d2800ba8        mov     x8, #0x5d                       // #93
  4000e4:       d2800000        mov     x0, #0x0                        // #0
  4000e8:       d4000001        svc     #0x0
  4000ec:       00000000        .inst   0x00000000 ; undefined
  4000f0:       004100f8        .inst   0x004100f8 ; undefined
  4000f4:       00000000        .inst   0x00000000 ; undefined

Vidíme, že programová smyčka je skutečně a jednu instrukci kratší.

7. Porovnání verze pro ARM32 s verzí pro AArch64

Opět si ukažme, do jaké míry se odlišuje zápis programu určený pro 32bitovou architekturu ARM32 se zápisem toho samého programu určeného pro AArch64:

8. Test na ukončení smyčky na začátku každé iterace

Další úprava programové smyčky spočívá v testu ukončení iterací na jejím začátku. To je opět téma, kterému jsme se již věnovali, takže si pouze ukažme, jakým způsobem je možné tuto programovou smyčku implementovat na architektuře AArch64. Samotná smyčka končí nepodmíněným skokem na její začátek; instrukce nepodmíněného skoku se jmenuje B („branch“):

loop:
        sub   x2, x2, #1             // zmenseni pocitadla
        cmp   x2, #0                 // otestovani, zda jsme jiz nedosahli nuly
        beq   konec                  // pokud jsme se dostali k nule, konec smycky
        strb  w3, [x1]               // zapis znaku do bufferu
        add   x1, x1, #1             // uprava ukazatele do bufferu
        b     loop                   // nepodmineny skok na zacatek smycky
konec:

Samozřejmě je opět možné vynechat instrukci CMP a nastavit příznak zero přímo při dekrementaci počitadla:

loop:
        subs  x2, x2, #1             // zmenseni pocitadla a nastaveni priznaku
        beq   konec                  // pokud jsme se dostali k nule, konec smycky
        strb  w3, [x1]               // zapis znaku do bufferu
        add   x1, x1, #1             // uprava ukazatele do bufferu
        b     loop                   // nepodmineny skok na zacatek smycky
konec:

9. První varianta smyčky s testem na začátku

Pro úplnost si ukažme celý zdrojový kód s první variantou počítané programové smyčky s testem provedeným na začátku každé iterace:

# asmsyntax=as
 
# Testovaci program naprogramovany v assembleru GNU as
# - pocitana programova smycka s testem na zacatku
# - uprava pro mikroprocesory s architekturou AArch64
#
# Autor: Pavel Tisnovsky
 
 
 
# Linux kernel system call table
sys_exit   = 93
sys_write  = 64

# List of syscalls for AArch64:
# https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h
 
# Dalsi konstanty pouzite v programu - standardni streamy
std_input  = 0
std_output = 1

# pocet opakovani znaku
rep_count  = 40
 
 
 
#-----------------------------------------------------------------------------
.section .data
 
 
 
#-----------------------------------------------------------------------------
.section .bss
        .lcomm buffer, rep_count     // rezervace bufferu pro vystup
 
 
 
#-----------------------------------------------------------------------------
.section .text
        .global _start               // tento symbol ma byt dostupny i linkeru
 
_start:
        ldr   x1, =buffer            // zapis se bude provadet do tohoto bufferu
        mov   x2, #rep_count+1       // pocet opakovani znaku
        mov   w3, #'*'               // zapisovany znak
loop:
        sub   x2, x2, #1             // zmenseni pocitadla
        cmp   x2, #0                 // otestovani, zda jsme jiz nedosahli nuly
        beq   konec                  // pokud jsme se dostali k nule, konec smycky
        strb  w3, [x1]               // zapis znaku do bufferu
        add   x1, x1, #1             // uprava ukazatele do bufferu
        b     loop                   // nepodmineny skok na zacatek smycky
konec:
 
        mov   x8, #sys_write         // cislo syscallu pro funkci "write"
        mov   x0, #std_output        // standardni vystup
        ldr   x1, =buffer            // adresa retezce, ktery se ma vytisknout
        mov   x2, #rep_count         // pocet znaku, ktere se maji vytisknout
        svc   0                      // volani Linuxoveho kernelu
 
        mov   x8, #sys_exit          // cislo sycallu pro funkci "exit"
        mov   x0, #0                 // exit code = 0
        svc   0                      // volani Linuxoveho kernelu

10. Porovnání s ARM32

Podobně, jako tomu bylo v předchozích demonstračních příkladech, i u počítané smyčky s testem na začátku si ukážeme porovnání se zdrojovým kódem určeným pro 32bitové mikroprocesory s původní RISCovou instrukční sadou ARM32. Vidíme, že rozdíly jsou nepatrné, pouze se použijí 64bitové registry (kromě registru obsahujícího kód zapisovaného znaku, což je dáno nutností použití instrukce strb) a syntaxe je čitelnější (odlišné znaky pro poznámky, prefixy konstant atd.):

11. Druhá varianta smyčky s testem na začátku

Druhá varianta počítané programové smyčky s testem na začátku vypadá po přepisu do assembleru pro architekturu AArch64 následovně. Povšimněte si, že programová smyčka začíná dvojicí instrukcí subs+beq:

# asmsyntax=as
 
# Testovaci program naprogramovany v assembleru GNU as
# - pocitana programova smycka s testem na zacatku
# - uprava pro mikroprocesory s architekturou AArch64
#
# Autor: Pavel Tisnovsky
 
 
 
# Linux kernel system call table
sys_exit   = 93
sys_write  = 64

# List of syscalls for AArch64:
# https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h
 
# Dalsi konstanty pouzite v programu - standardni streamy
std_input  = 0
std_output = 1

# pocet opakovani znaku
rep_count  = 40
 
 
 
#-----------------------------------------------------------------------------
.section .data
 
 
 
#-----------------------------------------------------------------------------
.section .bss
        .lcomm buffer, rep_count     // rezervace bufferu pro vystup
 
 
 
#-----------------------------------------------------------------------------
.section .text
        .global _start               // tento symbol ma byt dostupny i linkeru
 
_start:
        ldr   x1, =buffer            // zapis se bude provadet do tohoto bufferu
        mov   x2, #rep_count+1       // pocet opakovani znaku
        mov   w3, #'*'               // zapisovany znak
loop:
        subs  x2, x2, #1             // zmenseni pocitadla a nastaveni priznaku
        beq   konec                  // pokud jsme se dostali k nule, konec smycky
        strb  w3, [x1]               // zapis znaku do bufferu
        add   x1, x1, #1             // uprava ukazatele do bufferu
        b     loop                   // nepodmineny skok na zacatek smycky
konec:
 
        mov   x8, #sys_write         // cislo syscallu pro funkci "write"
        mov   x0, #std_output        // standardni vystup
        ldr   x1, =buffer            // adresa retezce, ktery se ma vytisknout
        mov   x2, #rep_count         // pocet znaku, ktere se maji vytisknout
        svc   0                      // volani Linuxoveho kernelu
 
        mov   x8, #sys_exit          // cislo sycallu pro funkci "exit"
        mov   x0, #0                 // exit code = 0
        svc   0                      // volani Linuxoveho kernelu

12. Porovnání s ARM32

Opět si, dnes již naposledy, porovnejme variantu téhož programu, poprvé vytvořeného pro 32bitové procesory s instrukční sadou ARM32, podruhé pro 64bitové procesory AArch64:

13. Instrukce CBZ a CBNZ

Instrukční sada AArch64 obsahuje i některé instrukce, které byly úspěšně otestovány v instrukčních sadách Thumb a Thumb-2. Do této oblasti spadají i nové typy podmíněných skoků. Ty se totiž v mnoha případech ukazují být kritickou částí kódu, protože zejména podmíněné skoky mohou přerušit jinak plynulý tok zpracovávaných instrukcí, takže se z ideálního stavu, kdy RISCové jádro díky existenci pipeline dokončí v každém cyklu jednu instrukci (v případě superskalárních čipů Cortex-A i více instrukcí) můžeme dostat do stavu, kdy podmíněný skok způsobí nutnost přerušit již zpracovávané instrukce a začít znovu (samozřejmě s latencí).

Při analýze reálných aplikací si tvůrci instrukční sady Thumb-2 všimli si, že se v programech velmi často vyskytuje sekvence instrukcí, které nejdřív porovnají obsah vybraného pracovního registru s nulou a posléze provedou podmíněný skok na základě toho, zda je onen pracovní registr skutečně nulový nebo naopak nenulový. Poměrně velké frekvenci této sekvence instrukcí se nelze ani divit, protože podobným způsobem mohou být implementovány například testy na hodnotu NULL, počítané smyčky, smyčky typu do-while v nichž je pravdivostní hodnota vyjádřena celým číslem, práce s ASCIIZ řetězci atd. Aby bylo možné zmenšit velikost binárního kódu programu a současně ho i urychlit, byly do instrukční sady Thumb-2 přidány dvě nové instrukce, které nejprve provedou porovnání pracovního registru s nulou a poté provedou skok, pokud je registr nulový či naopak není nulový. Součástí instrukčního slova je přitom i krátký offset umožňující provést skok do vzdálenosti PC+4 až PC+130.

První z těchto instrukcí provede skok, pokud je vybraný pracovní registr nulový:

CBZ Rn, offset   ; compare and branch if zero

Tato instrukce je ekvivalentní delší sekvenci:

CMP Rn, #0
BEQ label

Druhá instrukce provádí skok v přesně opačném případě, tj. tehdy, když má registr nenulovou hodnotu:

CBNZ Rn, offset   ; compare and branch if non zero

Ekvivalentní zápis:

CMP Rn, #0
BNE label

14. Úprava prvního demonstračního příkladu použitím instrukce CBNZ

Vzhledem k tomu, že se instrukce CBZ a CBNZ mohou použít i u 64bitové architektury AArch64, upravíme si první demonstrační příklad takovým způsobem, aby se v něm tyto instrukce využily. To znamená, že se namísto sekvence instrukcí:

        mov   x2, #rep_count         // pocet opakovani programove smycky
loop:
        ...
        ...
        ...
        sub   x2, x2, #1             // zmenseni pocitadla
        cmp   x2, #0                 // otestovani, zda jsme jiz nedosahli nuly
        bne   loop                   // pokud jsme se nedostali k nule, skok na zacatek smycky

Použije nová sekvence:

        mov   x2, #rep_count         // pocet opakovani programove smycky
loop:
        ...
        ...
        ...
        sub   x2, x2, #1             // zmenseni pocitadla a soucasne nastaveni priznaku
        cbnz  x2, loop               // pokud jsme se nedostali k nule, skok na zacatek smycky

Vidíme, že se nám podařilo smyčku zkrátit o jednu instrukci, takže jsme se vlastně dostali do stejné situace, jako při použití dvojice subs + podmíněný skok (jinými slovy – zde nám instrukce CBNZ vlastně příliš nepomohla):

        mov   x2, #rep_count         // pocet opakovani programove smycky
loop:
        ...
        ...
        ...
        subs  x2, x2, #1             // zmenseni pocitadla a soucasne nastaveni priznaku
        bne   loop                   // pokud jsme se nedostali k nule, skok na zacatek smycky

Podívejme se nyní na způsob zařazení instrukce CBNZ do celého programu, který po svém spuštění vygeneruje řetězec se čtyřiceti hvězdičkami, který následně vytiskne na standardní výstup:

# asmsyntax=as
 
# Testovaci program naprogramovany v assembleru GNU as
# - pocitana programova smycka realizovana instrukci CBNZ
# - uprava pro mikroprocesory s architekturou AArch64
#
# Autor: Pavel Tisnovsky
 
 
 
# Linux kernel system call table
sys_exit   = 93
sys_write  = 64

# List of syscalls for AArch64:
# https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h
 
# Dalsi konstanty pouzite v programu - standardni streamy
std_input  = 0
std_output = 1

# pocet opakovani znaku
rep_count  = 40
 
 
 
#-----------------------------------------------------------------------------
.section .data
 
 
 
#-----------------------------------------------------------------------------
.section .bss
        .lcomm buffer, rep_count     // rezervace bufferu pro vystup
 
 
 
#-----------------------------------------------------------------------------
.section .text
        .global _start               // tento symbol ma byt dostupny i linkeru
 
_start:
        ldr   x1, =buffer            // zapis se bude provadet do tohoto bufferu
        mov   x2, #rep_count         // pocet opakovani znaku
        mov   w3, #'*'               // zapisovany znak
loop:
        strb  w3, [x1]               // zapis znaku do bufferu
        add   x1, x1, #1             // uprava ukazatele do bufferu
        sub   x2, x2, #1             // zmenseni pocitadla a soucasne nastaveni priznaku
        cbnz  x2, loop               // pokud jsme se nedostali k nule, skok na zacatek smycky
 
        mov   x8, #sys_write         // cislo syscallu pro funkci "write"
        mov   x0, #std_output        // standardni vystup
        ldr   x1, =buffer            // adresa retezce, ktery se ma vytisknout
        mov   x2, #rep_count         // pocet znaku, ktere se maji vytisknout
        svc   0                      // volani Linuxoveho kernelu
 
        mov   x8, #sys_exit          // cislo sycallu pro funkci "exit"
        mov   x0, #0                 // exit code = 0
        svc   0                      // volani Linuxoveho kernelu

15. Odkazy na Internetu

  1. Cortex-A35
    https://www.arm.com/products/processors/cortex-a/cortex-a35-processor.php
  2. Cortex-A53
    https://www.arm.com/products/processors/cortex-a/cortex-a53-processor.php
  3. Cortex-A57
    https://www.arm.com/products/processors/cortex-a/cortex-a57-processor.php
  4. Cortex-A72
    https://www.arm.com/products/processors/cortex-a/cortex-a72-processor.php
  5. Cortex-A73
    https://www.arm.com/products/processors/cortex-a/cortex-a73-processor.php
  6. System cally pro AArch64 na Linuxu
    https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h
  7. Architectures/AArch64 (FedoraProject.org)
    https://fedoraproject.org/wiki/Architectures/AArch64
  8. SIG pro AArch64 (CentOS)
    https://wiki.centos.org/SpecialInterestGroup/AltArch/AArch64
  9. The ARMv8 instruction sets
    http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch05s01.html
  10. A64 Instruction Set
    https://developer.arm.com/products/architecture/instruction-sets/a64-instruction-set
  11. Switching between the instruction sets
    http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch05s01.html
  12. The A64 instruction set
    http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch05s01.html
  13. Introduction to ARMv8 64-bit Architecture
    https://quequero.org/2014/04/introduction-to-arm-architecture/
  14. MCU market turns to 32-bits and ARM
    http://www.eetimes.com/document.asp?doc_id=1280803
  15. Cortex-M0 Processor (ARM Holdings)
    http://www.arm.com/products/processors/cortex-m/cortex-m0.php
  16. Cortex-M0+ Processor (ARM Holdings)
    http://www.arm.com/products/processors/cortex-m/cortex-m0plus.php
  17. ARM Processors in a Mixed Signal World
    http://www.eeweb.com/blog/arm/arm-processors-in-a-mixed-signal-world
  18. ARM Architecture (Wikipedia)
    https://en.wikipedia.org/wiki/ARM_architecture
  19. ARM Documentation: B, BL, BX, BLX, and BXJ
    http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0204j/Cihfddaf.html
  20. Branch and Call Sequences Explained
    https://community.arm.com/groups/processors/blog/2013/09/25/branch-and-call-sequences-explained
  21. Improving ARM Code Density and Performance
    New Thumb Extensions to the ARM Architecture Richard Phelan
  22. Aarch64 Register and Instruction Quick Start
    https://wiki.cdot.senecacollege.ca/wiki/Aarch64_Register_and_Instruction_Quick_Start
  23. Exploring AArch64 assembler – Chapter 1
    http://thinkingeek.com/2016/10/08/exploring-aarch64-assembler-chapter1/
  24. Exploring AArch64 assembler – Chapter 2
    http://thinkingeek.com/2016/10/08/exploring-aarch64-assembler-chapter-2/
  25. The ARM Processor Architecture
    http://www.arm.com/products/processors/technologies/instruction-set-architectures.php
  26. Thumb-2 instruction set
    http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0344c/Beiiegaf.html
  27. Introduction to ARM thumb
    http://www.eetimes.com/discussion/other/4024632/Introduction-to-ARM-thumb
  28. ARM, Thumb, and ThumbEE instruction sets
    http://www.keil.com/support/man/docs/armasm/armasm_CEGBEIJB.htm
  29. An Introduction to ARM Assembly Language
    http://dev.emcelettronica.com/introduction-to-arm-assembly-language
  30. Processors – ARM
    http://www.arm.com/products/processors/index.php
  31. The ARM Instruction Set
    http://simplemachines.it/doc/arm_inst.pdf
  32. ARM Architecture (Wikipedia)
    http://en.wikipedia.org/wiki/ARM_architecture
  33. C Functions Without Arguments
    https://eklitzke.org/c-functions-without-arguments
  34. GNU Assembler Examples
    http://cs.lmu.edu/~ray/notes/gasexamples/
  35. Simply FPU
    http://www.website.masmforum.com/tutorials/fptute/
  36. Art of Assembly language programming: The 80×87 Floating Point Coprocessors
    https://courses.engr.illinois.edu/ece390/books/artofasm/CH14/CH14-3.html
  37. Art of Assembly language programming: The FPU Instruction Set
    https://courses.engr.illinois.edu/ece390/books/artofasm/CH14/CH14-4.html
  38. INTEL 80387 PROGRAMMER’S REFERENCE MANUAL
    http://www.ragestorm.net/downloads/387intel.txt
  39. x86 Instruction Set Reference: FLD
    http://x86.renejeschke.de/html/file_module_x86_id_100.html
  40. x86 Instruction Set Reference: FLD1/FLDL2T/FLDL2E/FLDPI/FLDLG2/FLDLN2/FLDZ
    http://x86.renejeschke.de/html/file_module_x86_id_101.html
  41. x86 Instruction Set Reference: FLD
    http://x86.renejeschke.de/html/file_module_x86_id_100.html
  42. x86 Instruction Set Reference: FST/FSTP
    http://x86.renejeschke.de/html/file_module_x86_id_117.html
  43. x86 Instruction Set Reference: BTC
    http://x86.renejeschke.de/html/file_module_x86_id_23.html
  44. x86 Instruction Set Reference: BTR
    http://x86.renejeschke.de/html/file_module_x86_id_24.html
  45. x86 Instruction Set Reference: BTS
    http://x86.renejeschke.de/html/file_module_x86_id_25.html
  46. x86 Instruction Set Reference: BSF
    http://x86.renejeschke.de/html/file_module_x86_id_19.html
  47. x86 Instruction Set Reference: BSR
    http://x86.renejeschke.de/html/file_module_x86_id_20.html
  48. x86 Instruction Set Reference: BSWAP
    http://x86.renejeschke.de/html/file_module_x86_id_21.html
  49. x86 Instruction Set Reference: XCHG
    http://x86.renejeschke.de/html/file_module_x86_id_328.html
  50. x86 Instruction Set Reference: SETcc
    http://x86.renejeschke.de/html/file_module_x86_id_288.html
  51. X86 Assembly/Arithmetic
    https://en.wikibooks.org/wiki/X86_Assembly/Arithmetic
  52. Art of Assembly – Arithmetic Instructions
    http://oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter_6/CH06-2.html
  53. The GNU Assembler Tutorial
    http://tigcc.ticalc.org/doc/gnuasm.html
  54. The GNU Assembler – macros
    http://tigcc.ticalc.org/doc/gnuasm.html#SEC109
  55. ARM subroutines & program stack
    http://www.toves.org/books/armsub/
  56. Generating Mixed Source and Assembly List using GCC
    http://www.systutorials.com/240/generate-a-mixed-source-and-assembly-listing-using-gcc/
  57. Calling subroutines
    http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.kui0100a/armasm_cihcfigg.htm
  58. ARM Assembly Language Programming
    http://peter-cockerell.net/aalp/html/frames.html
  59. ASM Flags
    http://www.cavestory.org/guides/csasm/guide/asm_flags.html
  60. Status Register
    https://en.wikipedia.org/wiki/Status_register
  61. Intel x86 JUMP quick reference
    http://unixwiz.net/techtips/x86-jumps.html
  62. Linux assemblers: A comparison of GAS and NASM
    http://www.ibm.com/developerworks/library/l-gas-nasm/index.html
  63. Programovani v assembleru na OS Linux
    http://www.cs.vsb.cz/grygarek/asm/asmlinux.html
  64. Is it worthwhile to learn x86 assembly language today?
    https://www.quora.com/Is-it-worthwhile-to-learn-x86-assembly-language-today?share=1
  65. Why Learn Assembly Language?
    http://www.codeproject.com/Articles/89460/Why-Learn-Assembly-Language
  66. Is Assembly still relevant?
    http://programmers.stackexchange.com/questions/95836/is-assembly-still-relevant
  67. Why Learning Assembly Language Is Still a Good Idea
    http://www.onlamp.com/pub/a/onlamp/2004/05/06/writegreatcode.html
  68. Assembly language today
    http://beust.com/weblog/2004/06/23/assembly-language-today/
  69. Assembler: Význam assembleru dnes
    http://www.builder.cz/rubriky/assembler/vyznam-assembleru-dnes-155960cz
  70. Assembler pod Linuxem
    http://phoenix.inf.upol.cz/linux/prog/asm.html
  71. AT&T Syntax versus Intel Syntax
    https://www.sourceware.org/binutils/docs-2.12/as.info/i386-Syntax.html
  72. Linux Assembly website
    http://asm.sourceforge.net/
  73. Using Assembly Language in Linux
    http://asm.sourceforge.net/articles/linasm.html
  74. vasm
    http://sun.hasenbraten.de/vasm/
  75. vasm – dokumentace
    http://sun.hasenbraten.de/vasm/release/vasm.html
  76. The Yasm Modular Assembler Project
    http://yasm.tortall.net/
  77. 680×0:AsmOne
    http://www.amigacoding.com/index.php/680×0:AsmOne
  78. ASM-One Macro Assembler
    http://en.wikipedia.org/wiki/ASM-One_Macro_Assembler
  79. ASM-One pages
    http://www.theflamearrows.info/documents/asmone.html
  80. Základní informace o ASM-One
    http://www.theflamearrows.info/documents/asminfo.html
  81. Linux Syscall Reference
    http://syscalls.kernelgrok.com/
  82. Programming from the Ground Up Book – Summary
    http://savannah.nongnu.org/projects/pgubook/
  83. IBM System 360/370 Compiler and Historical Documentation
    http://www.edelweb.fr/Simula/
  84. IBM 700/7000 series
    http://en.wikipedia.org/wiki/IBM_700/7000_series
  85. IBM System/360
    http://en.wikipedia.org/wiki/IBM_System/360
  86. IBM System/370
    http://en.wikipedia.org/wiki/IBM_System/370
  87. Mainframe family tree and chronology
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_FT1.html
  88. 704 Data Processing System
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_PP704.html
  89. 705 Data Processing System
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_PP705.html
  90. The IBM 704
    http://www.columbia.edu/acis/history/704.html
  91. IBM Mainframe album
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_album.html
  92. Osmibitové muzeum
    http://osmi.tarbik.com/
  93. Tesla PMI-80
    http://osmi.tarbik.com/cssr/pmi80.html
  94. PMI-80
    http://en.wikipedia.org/wiki/PMI-80
  95. PMI-80
    http://www.old-computers.com/museum/computer.asp?st=1&c=1016
  96. The 6502 overflow flag explained mathematically
    http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html
  97. X86 Opcode and Instruction Reference
    http://ref.x86asm.net/coder32.html