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
11. Druhá varianta smyčky s testem na začátku
14. Úprava prvního demonstračního příkladu použitím instrukce CBNZ
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
- Cortex-A35
https://www.arm.com/products/processors/cortex-a/cortex-a35-processor.php - Cortex-A53
https://www.arm.com/products/processors/cortex-a/cortex-a53-processor.php - Cortex-A57
https://www.arm.com/products/processors/cortex-a/cortex-a57-processor.php - Cortex-A72
https://www.arm.com/products/processors/cortex-a/cortex-a72-processor.php - Cortex-A73
https://www.arm.com/products/processors/cortex-a/cortex-a73-processor.php - 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 - ARM Documentation: B, BL, BX, BLX, and BXJ
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0204j/Cihfddaf.html - Branch and Call Sequences Explained
https://community.arm.com/groups/processors/blog/2013/09/25/branch-and-call-sequences-explained - Improving ARM Code Density and Performance
New Thumb Extensions to the ARM Architecture Richard Phelan - Aarch64 Register and Instruction Quick Start
https://wiki.cdot.senecacollege.ca/wiki/Aarch64_Register_and_Instruction_Quick_Start - 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/ - The ARM Processor Architecture
http://www.arm.com/products/processors/technologies/instruction-set-architectures.php - Thumb-2 instruction set
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0344c/Beiiegaf.html - Introduction to ARM thumb
http://www.eetimes.com/discussion/other/4024632/Introduction-to-ARM-thumb - ARM, Thumb, and ThumbEE instruction sets
http://www.keil.com/support/man/docs/armasm/armasm_CEGBEIJB.htm - An Introduction to ARM Assembly Language
http://dev.emcelettronica.com/introduction-to-arm-assembly-language - Processors - ARM
http://www.arm.com/products/processors/index.php - The ARM Instruction Set
http://simplemachines.it/doc/arm_inst.pdf - ARM Architecture (Wikipedia)
http://en.wikipedia.org/wiki/ARM_architecture - C Functions Without Arguments
https://eklitzke.org/c-functions-without-arguments - GNU Assembler Examples
http://cs.lmu.edu/~ray/notes/gasexamples/ - Simply FPU
http://www.website.masmforum.com/tutorials/fptute/ - Art of Assembly language programming: The 80x87 Floating Point Coprocessors
https://courses.engr.illinois.edu/ece390/books/artofasm/CH14/CH14-3.html - Art of Assembly language programming: The FPU Instruction Set
https://courses.engr.illinois.edu/ece390/books/artofasm/CH14/CH14-4.html - INTEL 80387 PROGRAMMER'S REFERENCE MANUAL
http://www.ragestorm.net/downloads/387intel.txt - x86 Instruction Set Reference: FLD
http://x86.renejeschke.de/html/file_module_x86_id_100.html - x86 Instruction Set Reference: FLD1/FLDL2T/FLDL2E/FLDPI/FLDLG2/FLDLN2/FLDZ
http://x86.renejeschke.de/html/file_module_x86_id_101.html - x86 Instruction Set Reference: FLD
http://x86.renejeschke.de/html/file_module_x86_id_100.html - x86 Instruction Set Reference: FST/FSTP
http://x86.renejeschke.de/html/file_module_x86_id_117.html - x86 Instruction Set Reference: BTC
http://x86.renejeschke.de/html/file_module_x86_id_23.html - x86 Instruction Set Reference: BTR
http://x86.renejeschke.de/html/file_module_x86_id_24.html - x86 Instruction Set Reference: BTS
http://x86.renejeschke.de/html/file_module_x86_id_25.html - x86 Instruction Set Reference: BSF
http://x86.renejeschke.de/html/file_module_x86_id_19.html - x86 Instruction Set Reference: BSR
http://x86.renejeschke.de/html/file_module_x86_id_20.html - x86 Instruction Set Reference: BSWAP
http://x86.renejeschke.de/html/file_module_x86_id_21.html - x86 Instruction Set Reference: XCHG
http://x86.renejeschke.de/html/file_module_x86_id_328.html - x86 Instruction Set Reference: SETcc
http://x86.renejeschke.de/html/file_module_x86_id_288.html - X86 Assembly/Arithmetic
https://en.wikibooks.org/wiki/X86_Assembly/Arithmetic - Art of Assembly - Arithmetic Instructions
http://oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter_6/CH06-2.html - The GNU Assembler Tutorial
http://tigcc.ticalc.org/doc/gnuasm.html - The GNU Assembler - macros
http://tigcc.ticalc.org/doc/gnuasm.html#SEC109 - ARM subroutines & program stack
http://www.toves.org/books/armsub/ - Generating Mixed Source and Assembly List using GCC
http://www.systutorials.com/240/generate-a-mixed-source-and-assembly-listing-using-gcc/ - Calling subroutines
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.kui0100a/armasm_cihcfigg.htm - ARM Assembly Language Programming
http://peter-cockerell.net/aalp/html/frames.html - ASM Flags
http://www.cavestory.org/guides/csasm/guide/asm_flags.html - Status Register
https://en.wikipedia.org/wiki/Status_register - Intel x86 JUMP quick reference
http://unixwiz.net/techtips/x86-jumps.html - Linux assemblers: A comparison of GAS and NASM
http://www.ibm.com/developerworks/library/l-gas-nasm/index.html - Programovani v assembleru na OS Linux
http://www.cs.vsb.cz/grygarek/asm/asmlinux.html - Is it worthwhile to learn x86 assembly language today?
https://www.quora.com/Is-it-worthwhile-to-learn-x86-assembly-language-today?share=1 - Why Learn Assembly Language?
http://www.codeproject.com/Articles/89460/Why-Learn-Assembly-Language - Is Assembly still relevant?
http://programmers.stackexchange.com/questions/95836/is-assembly-still-relevant - Why Learning Assembly Language Is Still a Good Idea
http://www.onlamp.com/pub/a/onlamp/2004/05/06/writegreatcode.html - Assembly language today
http://beust.com/weblog/2004/06/23/assembly-language-today/ - Assembler: Význam assembleru dnes
http://www.builder.cz/rubriky/assembler/vyznam-assembleru-dnes-155960cz - Assembler pod Linuxem
http://phoenix.inf.upol.cz/linux/prog/asm.html - AT&T Syntax versus Intel Syntax
https://www.sourceware.org/binutils/docs-2.12/as.info/i386-Syntax.html - Linux Assembly website
http://asm.sourceforge.net/ - Using Assembly Language in Linux
http://asm.sourceforge.net/articles/linasm.html - vasm
http://sun.hasenbraten.de/vasm/ - vasm – dokumentace
http://sun.hasenbraten.de/vasm/release/vasm.html - The Yasm Modular Assembler Project
http://yasm.tortall.net/ - 680x0:AsmOne
http://www.amigacoding.com/index.php/680x0:AsmOne - ASM-One Macro Assembler
http://en.wikipedia.org/wiki/ASM-One_Macro_Assembler - ASM-One pages
http://www.theflamearrows.info/documents/asmone.html - Základní informace o ASM-One
http://www.theflamearrows.info/documents/asminfo.html - Linux Syscall Reference
http://syscalls.kernelgrok.com/ - Programming from the Ground Up Book - Summary
http://savannah.nongnu.org/projects/pgubook/ - IBM System 360/370 Compiler and Historical Documentation
http://www.edelweb.fr/Simula/ - IBM 700/7000 series
http://en.wikipedia.org/wiki/IBM_700/7000_series - IBM System/360
http://en.wikipedia.org/wiki/IBM_System/360 - IBM System/370
http://en.wikipedia.org/wiki/IBM_System/370 - Mainframe family tree and chronology
http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_FT1.html - 704 Data Processing System
http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_PP704.html - 705 Data Processing System
http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_PP705.html - The IBM 704
http://www.columbia.edu/acis/history/704.html - IBM Mainframe album
http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_album.html - Osmibitové muzeum
http://osmi.tarbik.com/ - Tesla PMI-80
http://osmi.tarbik.com/cssr/pmi80.html - PMI-80
http://en.wikipedia.org/wiki/PMI-80 - PMI-80
http://www.old-computers.com/museum/computer.asp?st=1&c=1016 - The 6502 overflow flag explained mathematically
http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html - X86 Opcode and Instruction Reference
http://ref.x86asm.net/coder32.html