Egy bemenetellenőrző rutin hibás adat esetén azonnal kilép, érvényes adat esetén pedig folytatja a feldolgozást. Itt nem elég két érték közül választani: bizonyos utasításokat egyáltalán nem szabad végrehajtani. Ehhez elágazásra (branch) van szükség.
A Feltételes végrehajtás: CSET/CSEL és feltételkódok alfejezetben az A64 feltételkódjai az eredményként kiválasztott adatot befolyásolták. Most ugyanezekkel a feltételekkel a következő végrehajtandó utasítást választjuk ki.
Az alfejezet teljes egészében A64 ISA, ARMv8-A, little-endian
Linux környezetre vonatkozik. Előfeltétel az A64
CMP és az NZCV állapotjelzők értelmezése. A labor célja a
vezérlés helyességének ellenőrzése, nem a teljesítménymérés.
A vezérlésátadás (control transfer) megváltoztatja a következő végrehajtandó utasítás címét. Az A64 utasítások 4 bájtosak, ezért szokásos, szekvenciális végrehajtáskor a következő utasítás címe az aktuális cím plusz 4 bájt.
Az A64 közvetlen elágazásainál a cél az utasítás helyéhez viszonyított, előjeles eltolásból származik. A forráskódban ezt többnyire címkével (label) adjuk meg:
b lab_exit feltétel nélkül átadja a
vezérlést;b.eq .Lzero csak teljesülő EQ
feltételnél ugrik;cbz x0, .Lzero az A64 X0 regiszter
nulla értékét vizsgálja.Ha egy feltételes elágazás nem teljesül, a végrehajtás a következő utasítással folytatódik. Ez a továbbhaladó ág (fall-through path). A64-en nincs késleltetett elágazási rés (branch delay slot): teljesülő ugrás után a közvetlenül következő utasítást nem kell még végrehajtani.
| A64 utasítás | Döntés alapja | Olvassa az NZCV-t? | Módosítja az NZCV-t? |
|---|---|---|---|
B |
Nincs feltétel | Nem | Nem |
B.cond |
A megadott feltételkód | Igen | Nem |
CBZ Wn/Xn |
A regiszter értéke nulla | Nem | Nem |
CBNZ Wn/Xn |
A regiszter értéke nem nulla | Nem | Nem |
Az A64 B egyszerű ugrás: nem ment visszatérési címet. A
függvényhívás ettől eltérő szerződését a Hívás és
visszatérés: BL/RET bevezető mintája tárgyalja.
Az A64 B.cond jelölésben a cond helyére
konkrét feltételkód kerül, például EQ, NE,
LT vagy HI. A GNU assemblerben kisbetűs
írásmódot használunk: például A64 b.lt.
Az A64 cmp x0, #10 után:
| A64 elágazás | Jelentés |
|---|---|
b.eq |
Az A64 X0 értéke egyenlő 10-zel |
b.ne |
Az A64 X0 értéke nem egyenlő 10-zel |
b.lt |
Az A64 X0 előjelesen kisebb
10-nél |
b.gt |
Az A64 X0 előjelesen nagyobb
10-nél |
b.lo |
Az A64 X0 előjel nélkül kisebb
10-nél |
b.hi |
Az A64 X0 előjel nélkül nagyobb
10-nél |
Az A64 CMP immediate-változata 12 bites, előjel nélküli
konstanst fogad el, opcionális 12 bites balra tolással. A példában
szereplő 10 közvetlenül kódolható.
Az összehasonlítás és az A64 B.cond között nem
feltétlenül kell közvetlen szomszédságnak lennie. A követelmény az, hogy
közben semmi ne írja felül a szükséges NZCV-állapotot. A közbeiktatott
A64 ADDS például új jelzőket állítana elő, így az elágazás
már nem az eredeti összehasonlításra válaszolna.
Nullatesztnél az A64 CBZ és CBNZ egyszerűbb
mintázat:
cbz x0, cél ugyanazt a nulla/nem-nulla döntést
hozza, mint az A64 cmp x0, #0 és
b.eq cél;cbnz x0, cél a nem nulla értékre ugrik;Ez a megoldás külön összehasonlító utasítást takarít meg, de önmagában nem jelent bizonyított gyorsulást.
A regiszterszélesség a vizsgálat része. Az A64
cbz w0, cél csak az alsó 32 bitet vizsgálja; az A64
cbz x0, cél mind a 64 bitet. A
0x0000000100000000 bitmintánál az előbbi ugrik, az utóbbi
nem. Ez olvasási művelet: az A64 W0 operandus használata
itt nem nullázza az A64 X0 felső felét.
Az A64 elágazás forrásbeli címkéje nem tetszőleges teljes címként kerül az utasításba. A kódolás egy előjeles mezőt tartalmaz, amelynek értékét 4 bájttal kell megszorozni.
Legyen:
Ekkor:
Fontos: az alap maga az elágazási utasítás, nem a következő utasítás címe. A cél és az elágazás különbségének 4 bájttal oszthatónak kell lennie.
Egy bites kettes komplemens mezőnél:
Ebből az alábbi, elméleti, levezetett tartományok adódnak:
| A64 utasítás | Eltolásmező | Legkisebb eltolás [bájt] | Legnagyobb eltolás [bájt] |
|---|---|---|---|
B |
26 bit | −134217728 | +134217724 |
B.cond |
19 bit | −1048576 | +1048572 |
CBZ, CBNZ |
19 bit | −1048576 | +1048572 |
Az A64 B tehát megközelítőleg ±128 MiB, a többi
felsorolt elágazás megközelítőleg ±1 MiB távolságot ér el. A pozitív
szélsőérték mindkét esetben 4 bájttal kisebb a kerek határnál. Itt 1 MiB
= 1048576 bájt.
Ha egy feltételes cél kívül esik a rövid tartományon, gyakori átalakítás:
B
utasítást;B a távolabbi célra ugrik.Például az A64 b.gt lab_far helyett az A64
b.le .Lnear következhet, utána az A64
b lab_far, majd a .Lnear címke. Ez csak akkor
elég, ha az új A64 B saját helyétől mérve eléri a célt.
Még nagyobb távolsághoz hosszabb, célcímet előállító és közvetett ugrást végző utasítássor kell. Ilyen köztes ugrókódot a linker bizonyos esetekben létrehozhat; ezt gyakran veneer vagy thunk néven említik. Nem szabad minden elágazásra automatikus javítást feltételezni. A feltételes elágazás túlcsordulása hibaüzenethez vezethet. A linker konkrét viselkedését és az esetleges segédregiszter-használatot külön ellenőrizni kell.
A címke szimbolikus nevet rendel egy helyhez. A GNU assembler
A64-forrásában a .Lzero: sor nem utasítás, és önmagában nem
foglal kódbájtot. Az utána következő helyet nevezi el.
Ha az ugrás és célja ugyanabban a szekcióban található, az assembler rendszerint már ismeri a különbségüket, és közvetlenül kódolhatja azt. Másik objektumfájlban definiált cél esetén relokációs bejegyzést (relocation entry) hagy az objektumban.
A linker ekkor:
A GNU AArch64 ELF-eszközláncban az A64 B külső céljához
tipikusan R_AARCH64_JUMP26 relokáció tartozik. A következő
labor ezt külön objektumfájlokkal teszi megfigyelhetővé.
Munkakönyvtár: ~/arm-labor/a64/a018/ —
az alfejezet minden laborfájlja ide kerül, és minden parancs innen
futtatandó (a könyvtárrendet a Labor-alapkörnyezet alfejezet írja le).
mkdir -p ~/arm-labor/a64/a018
cd ~/arm-labor/a64/a018A program szerződése:
X0 előjeles, 64 bites bemenet;X2 előjel nélküli, 64 bites engedélyezési érték:
nulla esetén tiltott, minden más értéknél engedélyezett;A lab-branch.s fájl:
// A64 ISA, ARMv8-A; GNU as direktívák AArch64 ELF-hez.
.text
.p2align 2
.global _start
.type _start, %function
_start:
movz x0, #7
movz x2, #1
cbz x0, .Lzero
cmp x0, #0
b.lt .Lnegative
cmp x0, #10
b.gt .Llarge
cbnz x2, .Lenabled
movz w0, #3
b lab_exit
.Lenabled:
movz w0, #0
b lab_exit
.Lzero:
movz w0, #1
b lab_exit
.Lnegative:
movz w0, #2
b lab_exit
.Llarge:
movz w0, #4
b lab_exit
.size _start, .-_start
.section .note.GNU-stack, "", %progbitsA lab-exit.s fájl:
// A64 ISA, ARMv8-A; GNU as direktívák AArch64 ELF-hez.
.text
.p2align 2
.global lab_exit
.type lab_exit, %function
lab_exit:
movz x8, #93
svc #0
.size lab_exit, .-lab_exit
.section .note.GNU-stack, "", %progbitsA példában az A64 MOVZ 16 bites immediate-mezőjébe
minden konstans belefér. Az A64 MOVZ megengedett eltolásai
Wn cél esetén 0 vagy 16, Xn cél esetén 0, 16,
32 vagy 48 bit. Az A64 W0 írása nullázza az A64
X0 felső 32 bitjét, így a kilépési kód teljes
regiszterértéke is megfelelő.
Az A64 SVC 16 bites immediate-mezőjében a 0 kódolható. A
93-as rendszerhívásszám viszont nem ISA-tulajdonság, hanem a Linux
AArch64 rendszerhívási felületének exit azonosítója.
Target: qemu-user, AArch64 little-endian Linux; host: Ubuntu 24.04 x86_64.
aarch64-linux-gnu-as -EL -march=armv8-a \
-o lab-branch.o lab-branch.s
aarch64-linux-gnu-as -EL -march=armv8-a \
-o lab-exit.o lab-exit.s
aarch64-linux-gnu-ld -EL -e _start \
-o lab-branch lab-branch.o lab-exit.o
qemu-aarch64 ./lab-branch
lab_status=$?
printf 'status=%s\n' "$lab_status"Várt, nem ténylegesen mért kimenet:
status=0. A program saját maga nem ír szöveget; a
megjelenített sort a shell állítja elő.
A levezetett végrehajtási út:
X0 értéke 7, ezért az A64 CBZ nem
ugrik.B.cond sem ugrik: 7 nem negatív és nem
nagyobb 10-nél.X2 értéke 1, ezért az A64 CBNZ a
sikeres ágra ugrik.W0 értéke 0 lesz.B a külön objektumban definiált
lab_exit célra ugrik.Bemenet: A64 X0,
előjeles |
Engedély: A64 X2, előjel
nélküli |
Várt kilépési állapot |
|---|---|---|
| 7 | 1 | 0 |
| 0 | 1 | 1 |
| −1 | 1 | 2 |
| 7 | 0 | 3 |
| 11 | 1 | 4 |
A negatív ellenpróbához az A64 X0 inicializálását
cseréljük A64 movz x0, #1, majd A64
sub x0, xzr, x0 utasításokra. Az eredmény előjelesen −1.
Minden változtatás után ismételjük meg a teljes build–futtatás
lépéssort.
Target: a qemu-user labor AArch64 objektumai és ELF-fájlja; hostoldali vizsgálat:
aarch64-linux-gnu-objdump -dr lab-branch.o
aarch64-linux-gnu-readelf -rW lab-branch.o
aarch64-linux-gnu-objdump -d lab-branchAz első két parancs kimenetében a lab_exit szimbólumra
hivatkozó R_AARCH64_JUMP26 bejegyzéseket várunk. A végleges
ELF disassemblyjében az A64 B utasításoknak már a feloldott
lab_exit célra kell mutatniuk.
A konkrét címeket nem kell előre feltételezni: a saját linkerünk kimenetét vizsgáljuk. A címkülönbségből ellenőrizhető az eltolás. A kiírt utasításcímek nem jelentenek általánosan használható memóriacímeket.
Hibás irányú feltétel. Ha a nulla bemenet nem a
nullaágra jut, először ellenőrizzük, hogy nem A64 CBNZ
szerepel-e A64 CBZ helyett. Adatgyűjtés: disassembly és
kilépési állapot. Egyetlen mnemonic javítása után ismételjük meg a 0 és
7 bemenetes próbát.
Előjeles és előjel nélküli döntés összekeverése. A
−1 bitmintája előjel nélkül nagy pozitív szám. Az A64 B.LT
és B.LO ezért nem felcserélhető. A labor felső korlátjánál
az A64 B.GT A64 B.HI-ra cserélése viszont nem
feltétlenül látható: a korábbi negatívvizsgálat már kizárta az eltérő
eseteket. A teszteket mindig az adott pontra ténylegesen eljutó értékek
alapján válasszuk.
Helyes feltétel, rossz jelzők. Ha az összehasonlítás
és az A64 B.cond közé jelzőfrissítő utasítás került, a
disassemblyben keressük meg. Egyetlen változtatással távolítsuk el, majd
futtassuk újra a határértékeket.
Túl távoli cél. A tartományhibát nem oldja meg a címke átnevezése. Vizsgáljuk meg a relokációt és a szekcióelrendezést; ezután válasszunk hosszabb ugrásmintát.
Magyarázó (alapszint — cél:
megértés-ellenőrzés): Miért megfelelő az A64 CBNZ
az engedélyezéshez, és miért kell a felső korlát vizsgálatához más
mintázat? Mi történik, ha a bemenet és az engedélyezési érték egyaránt
nulla?
Implementációs (alapszint — cél: rutinépítés): Módosítsd az elfogadott tartományt 1–20-ra, a hibakódok megtartásával. Ellenőrizd a 0, 1, 20 és 21 bemeneteket engedélyezett állapotban.
Hibakeresési (középszint — cél: rutinépítés):
Állítsd az engedélyt az A64 movz x2, #1, lsl #32
utasítással. Ezután szándékosan cseréld az A64
cbnz x2, .Lenabled utasítást A64
cbnz w2, .Lenabled alakra. Jósolj kilépési állapotot, majd
a disassembly alapján magyarázd meg az eltérést.
Kódolási (középszint — cél:
megértés-ellenőrzés): Eléri-e az A64 B.cond a
saját címéhez képest +1048572, illetve +1048576 bájtra lévő célt? Hogyan
alakítanál át egy túl távoli A64 B.GT elágazást, ha a cél
az A64 B tartományán belül van?
1. megoldás — Segítség. Az engedélyezés egy bináris (nulla/nem-nulla) kérdés, a felső korlát viszont egy reláció (kisebb/nagyobb) — melyik A64-mintázat olvassa csak a Z flaget, és melyik igényli NZCV teljes kiértékelését?
1. megoldás — Teljes megoldás. Az engedélyezés
kizárólag nulla/nem-nulla kérdés; az A64 CBNZ ezt NZCV
nélkül megoldja. A felső korlát relációt igényel, ezért A64
CMP és megfelelő A64 B.cond kell. Két nulla
bemenetnél az első A64 CBZ már a nullaágra ugrik, ezért a
várt állapot 1; az engedélyezést nem vizsgáljuk meg.
2. megoldás — Segítség. Csak a felső határt
összehasonlító immediate kell cserélni; ellenőrizd, hogy 20 belefér-e a
CMP immediate-kódolási korlátjába.
2. megoldás — Teljes megoldás. Az A64
cmp x0, #10 helyett A64 cmp x0, #20 szükséges.
A 20 belefér a 12 bites immediate-mezőbe. A várt állapotok rendre 1, 0,
0 és 4. A felső határ elfogadott, ezért marad az A64 B.GT,
nem A64 B.GE.
3. megoldás — Segítség. Nézd meg, mi kerül
X2 alsó 32, illetve felső 32 bitjébe a
movz ... lsl #32 után, és hogy a W2 nézet
melyik felet látja.
3. megoldás — Teljes megoldás. Az A64
X2 értéke 0x0000000100000000, alsó 32 bitje
nulla. Az A64 CBNZ X2 változattal a várt állapot 0; az A64
CBNZ W2 változattal 3. A hipotézist a disassembly
operandusszélessége ellenőrzi. Az egyetlen javítás az A64
X2 nézet visszaállítása.
4. megoldás — Segítség. Az A64 B.cond
19 bites, előjeles, 4 bájtra skálázott eltolást kódol — számítsd ki
ennek a mezőnek a legnagyobb pozitív értékét bájtban, és vesd össze a
két jelölt céllal.
4. megoldás — Teljes megoldás. Az első cél elérhető,
mert az eltolás osztva 4-gyel 262143, a 19 bites előjeles mező
legnagyobb értéke. A másodikhoz 262144 kellene, ami már nem fér bele. Az
átalakítás: A64 B.LE egy közeli címkére, utána A64
B a távoli célra, végül a közeli címke. Az új A64
B hatótávolságát a saját címétől számítjuk.
Az A64 elágazás a végrehajtás következő helyét választja ki. A64
B esetén nincs feltétel, A64 B.cond esetén az
NZCV dönt, míg az A64 CBZ/CBNZ közvetlenül egy regiszter
nulla vagy nem nulla értékét vizsgálja. A regiszterszélesség és az
előjeles értelmezés a helyesség része. A relatív cél az ugrás saját
címéhez képest számítandó, 4 bájtos lépésekben. A külön objektumban
található cél feloldása a linker feladata; a túl nagy távolságot nem
feltétlenül javítja automatikusan. A laborban kilépési állapotokkal és
disassemblyvel ellenőriztük az útvonalakat.
A linkek és a dokumentumverziók közös állapota az Olvasói útmutató forrásállapot-táblájában van; itt csak az alfejezet saját megállapításai szerepelnek.
B,
B.cond, CBZ és CBNZ kódolása és
szemantikája. Ellenőrizve: a link él (a
support.arm.com-ra irányított átirányítás megerősített), de
a fejezetszám és a kiadásbetű az Arm-oldalak közös korlátja miatt nem
ellenőrzött.Ellenőrzési hiányok, amelyek a fenti ellenőrzés után is fennmaradnak: a kód nem került futtatásra; a kimenetek levezetett várakozások. Az Ubuntu 24.04-ben telepített konkrét binutils-verzió hibaüzeneteit és távoli ugrásokhoz generált esetleges köztes kódját a hallgatói laborban kell ellenőrizni.