Egy minimumkeresésben vagy állapotérték előállításakor gyakori feladat: két érték összehasonlítása után vagy egy logikai eredményre, vagy a két érték egyikére van szükségünk. Ehhez nem feltétlenül kell megváltoztatni a program végrehajtási útvonalát. Az elágazásmentes kiválasztás (branchless selection) feltételtől függő adatot állít elő, miközben az utasítássor lineáris marad.
Az előfeltétel Az NZCV állapotjelzők és eredetük ismerete. Most azt vizsgáljuk, hogyan lesz az állapotjelzőkből C-szerű összehasonlítás, majd regiszterben tárolt eredmény.
Architektúra: az alfejezet valamennyi utasítása és regisztere A64 ISA, AArch64, ARMv8-A környezethez tartozik. Az assemblydirektívák GNU assemblerhez, AArch64/ELF célhoz tartoznak. A labor little-endian profilt használ; Cortex-M/Thumb-2 utasítás nem szerepel benne.
A feltételkód (condition code) az aktuális NZCV állapotjelzőkből képzett logikai feltételt jelöl. Nem tárol összehasonlítási operandusokat, és nem emlékszik azok C-típusára.
Az A64 CMP W1, W2 a 32 bites kivonás állapotjelzőit
állítja elő. Ugyanezekből a jelzőkből választhatunk előjeles vagy előjel
nélküli összehasonlítást. A választást a feltételkód végzi, nem a
CMP.
Az alábbi táblázat összehasonlítási jelentése akkor érvényes, ha a
jelzőket közvetlenül a megadott szélességű CMP a, b
állította elő, és azóta nem íródtak felül.
| A64-feltételkód | NZCV-feltétel | Jelentés CMP a, b után |
|---|---|---|
EQ |
Z = 1 | a == b |
NE |
Z = 0 | a != b |
CS, más néven HS |
C = 1 | Előjel nélkül a >= b |
CC, más néven LO |
C = 0 | Előjel nélkül a < b |
MI |
N = 1 | A kivonási eredmény legfelső bitje 1 |
PL |
N = 0 | A kivonási eredmény legfelső bitje 0 |
VS |
V = 1 | Előjeles túlcsordulás történt |
VC |
V = 0 | Nem történt előjeles túlcsordulás |
HI |
C = 1 és Z = 0 | Előjel nélkül a > b |
LS |
C = 0 vagy Z = 1 | Előjel nélkül a <= b |
GE |
N = V | Előjelesen a >= b |
LT |
N ≠ V | Előjelesen a < b |
GT |
Z = 0 és N = V | Előjelesen a > b |
LE |
Z = 1 vagy N ≠ V | Előjelesen a <= b |
AL, NV |
Mindig igaz | Az A64 feltételkiértékelésének különleges esetei |
A CS és HS, illetve a CC és
LO ugyanannak a kódnak két neve. Összehasonlításnál a
HS és LO elnevezés gyakran olvashatóbb.
Az NV történeti neve ellenére az A64 itt használt
feltételkiértékelésében nem „soha” jelentésű. Az A64
CSEL esetén az AL és NV egyaránt
az első forrás kiválasztását eredményezi; az A64 CSET
viszont egyiket sem fogadja el. Szokásos összehasonlításokhoz nincs
rájuk szükség.
Előjel nélküli kivonásnál C = 1 a kölcsönzés hiányát jelenti. Ez
közvetlenül megadja az a >= b feltételt. A szigorú
nagyobb relációhoz az egyenlőséget is ki kell zárni, ezért
HI feltétele C = 1 és Z = 0.
Előjeles összehasonlításnál a kivonás túlcsordulhat. Vegyük a következő, 32 bites, levezetett esetet:
a = 0x80000000, előjelesen −2147483648;b = 0x00000001, előjelesen 1;0x7fffffff.Ezért N = 0, Z = 0, C = 1, V = 1. Az N önmagában pozitív eredményt
sugallna, mégis a < b. A túlcsordulást figyelembe vevő
feltétel N ≠ V, vagyis LT.
Ugyanezek a bitminták előjel nélkül 2147483648 és 1 értékűek, ezért
LO hamis. Ez nem ellentmondás: más értelmezési tartományban
hasonlítunk.
Az A64 CSET egy feltétel igazságértékét írja
regiszterbe:
// A64 ISA, AArch64: 32 bites összehasonlítás és logikai eredmény.
cmp w1, w2
cset w3, ltA W3 értéke 1, ha a feltétel igaz, különben 0. Ez
megfelel egy előjeles C-összehasonlítás eredményének, ha az operandusok
32 bites előjeles értékeket jelentenek.
A CSET:
W célregiszter esetén a megfelelő X
regiszter felső 32 bitjét nullázza.A CSET utasításálnév (instruction
alias). Például az A64 CSET W3, LT mögött
CSINC W3, WZR, WZR, GE áll: az invertált feltétel igaz ágán
nulla, hamis ágán nulla plusz egy keletkezik. Emiatt az aliasokat
elrejtő disassembly eltérhet a forrásszövegtől.
Az A64 CSEL jelentése:
// A64 ISA, AArch64: ha LT igaz, W5 = W1; különben W5 = W2.
csel w5, w1, w2, ltAz első forrás az igaz, a második a hamis ághoz tartozik. Mindhárom
adatregiszter azonos szélességű: vagy W, vagy
X nézetű. A kiválasztás magukat a bitmintákat másolja;
nincs külön előjeles és előjel nélküli CSEL.
A CSEL nem módosítja az NZCV-t. Egyetlen összehasonlítás
után ezért több CSET és CSEL is használhatja
ugyanazt az állapotot.
Kódolási korlát: az A64 CSEL
adatforrásai regiszterek, nem közvetlen konstansok (immediate).
Nullához WZR vagy XZR használható; más
konstanshoz előbb regiszterértéket kell előállítani.
A C feltételes operátorának (conditional operator) egyszerű leképezése:
| C-kifejezés, megfelelő operandustípusokkal | A64-kiválasztás CMP W1, W2 után |
|---|---|
Előjeles a < b ? a : b |
CSEL W5, W1, W2, LT |
Előjel nélküli a < b ? a : b |
CSEL W5, W1, W2, LO |
Előjeles a >= b ? a : b |
CSEL W5, W1, W2, GE |
Előjel nélküli a >= b ? a : b |
CSEL W5, W1, W2, HS |
A C tényleges összehasonlítását a szokásos aritmetikai konverziók határozzák meg. Vegyes előjelű operandusoknál ne pusztán az egyik változó deklarációjából válassz feltételkódot.
A CSEL nem választ következő végrehajtási címet, ezért
ehhez a kiválasztáshoz nincs feltételes elágazás, amelyet az
elágazásbecslőnek (branch predictor) meg
kellene jósolnia.
Ettől azonban a megoldás nem automatikusan gyorsabb. Mindkét forrásérték előállítása munkát igényelhet, és az eredmény függ a jelzőktől, valamint a forrásregiszterektől. Az elágazásbecslés és az adatfüggőségek teljesítményhatását később tárgyaljuk.
Fontos szemantikai korlát: a C ?: csak a kiválasztott
operandusát értékeli ki. A p != NULL ? *p : 0 ezért nem
írható át általánosan feltétel nélküli betöltésre és utólagos
CSEL-re. A hibás memóriaelérés addigra már megtörténne.
Mellékhatásos függvényhívásoknál ugyanez az óvatosság szükséges.
Az elágazásmentesség önmagában állandó idejű, mellékcsatornákkal szemben védett végrehajtást sem garantál.
Munkakönyvtár: ~/arm-labor/a64/a017/ —
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/a017
cd ~/arm-labor/a64/a017A labor előjeles és előjel nélküli minimumot, két logikai eredményt,
valamint egy egyszerű if/else választását állítja elő.
Az alábbi teljes C-referencia legyen a lab-csel-ref.c
fájl:
#include <stdint.h>
int main(void)
{
int32_t a = INT32_MIN;
int32_t b = 1;
uint32_t ua = (uint32_t)a;
uint32_t ub = (uint32_t)b;
int signed_less = a < b;
int unsigned_less = ua < ub;
int32_t signed_min = a < b ? a : b;
uint32_t unsigned_min = ua < ub ? ua : ub;
int selected;
if (a < b)
selected = 100;
else
selected = 7;
return !(signed_less == 1 &&
unsigned_less == 0 &&
signed_min == INT32_MIN &&
unsigned_min == 1 &&
selected == 100);
}Az előjelesből előjel nélkülibe alakítás itt jól definiált C-művelet. A referencia szemantikai ellenőrzésre szolgál; nem állítjuk, hogy a fordító pontosan az alábbi kézi utasítássort állítja elő.
A teljes assemblyprogram legyen a lab-csel.S fájl:
// A64 ISA, ARMv8-A, little-endian Linux.
// Direktívák: GNU as, AArch64/ELF cél.
.arch armv8-a
.text
.balign 4
.global _start
.type _start, %function
_start:
movz w1, #0x8000, lsl #16
movz w2, #1
cmp w1, w2
cset w3, lt
cset w4, lo
csel w5, w1, w2, lt
csel w6, w1, w2, lo
movz w9, #100
movz w10, #7
csel w7, w9, w10, lt
lab_selected:
// Önellőrzés: W0 végül 0 siker, 1 eltérés esetén.
eor w0, w3, w2
orr w0, w0, w4
cmp w5, w1
cset w11, ne
orr w0, w0, w11
cmp w6, w2
cset w11, ne
orr w0, w0, w11
cmp w7, w9
cset w11, ne
orr w0, w0, w11
// AArch64 Linux exit: X8 = 93, X0 = kilépési állapot.
movz x8, #93
svc #0
.size _start, . - _start
.section .note.GNU-stack, "", %progbitsAz első utasítás a MOVZ 16. bitre tolt mezőjével
pontosan 0x80000000 értéket állít elő (a korlátokat lásd az
A64 immediate-korlátok összefoglalójában). A
MOVZ nem módosítja az NZCV-t, ezért a két konstans
betöltése nem rontja el a későbbi kiválasztást.
A Linux-híváshoz itt SVC #0 szerepel; az utasítás és a
Linux rendszerhívási szerződés nem ugyanaz a specifikációs réteg.
Cél: AArch64 Linux, QEMU user-mode; host: Ubuntu 24.04 x86_64. A korábban telepített cross-eszközláncot használjuk.
# Target: qemu-user / AArch64, little-endian.
aarch64-linux-gnu-as -EL -march=armv8-a -g \
-o lab-csel.o lab-csel.S
aarch64-linux-gnu-ld -EL -e _start \
-o lab-csel lab-csel.o
qemu-aarch64 ./lab-csel
printf 'assembly kilépés=%d\n' "$?"
# Ugyanazon cél C-referenciája; statikus linkelés.
aarch64-linux-gnu-gcc -std=c17 -O0 -Wall -Wextra \
-march=armv8-a -mlittle-endian -static \
lab-csel-ref.c -o lab-csel-ref
qemu-aarch64 ./lab-csel-ref
printf 'C kilépés=%d\n' "$?"Várt, nem futtatással igazolt eredmény: mindkét kilépési állapot 0. Maguk a programok nem írnak a szabványos kimenetre.
A lab_selected címkénél az ISA-szemantikából
levezetett állapot:
| A64-regiszter vagy jelző | Várt érték | Értelmezés |
|---|---|---|
W3 |
1 | Előjeles kisebb: igaz |
W4 |
0 | Előjel nélküli kisebb: hamis |
W5 |
0x80000000 |
Előjeles minimum: −2147483648 |
W6 |
1 | Előjel nélküli minimum |
W7 |
100 | Az if igaz ágának értéke |
| NZCV | N=0, Z=0, C=1, V=1 | Az első összehasonlítás jelzői |
A W5 írása miatt X5 = 0x0000000080000000.
Ez nem a negatív szám 64 bites előjelkiterjesztett
alakja.
Az első terminálban:
# Target: qemu-user / AArch64; induláskor debuggerre vár.
qemu-aarch64 -g 1234 ./lab-cselA második terminálban:
# Target: a fenti QEMU-folyamat AArch64 regiszterállapota.
gdb-multiarch -q ./lab-csel \
-ex 'target remote localhost:1234' \
-ex 'break lab_selected' \
-ex 'continue' \
-ex 'info registers x3 x4 x5 x6 x7 cpsr' \
-ex 'p/x ($cpsr & 0xf0000000)'A maszkolt jelzőérték várhatóan 0x30000000. Ezután a GDB
continue parancsa végigfuttatja az önellenőrzést; a várt
kilépési állapot továbbra is 0.
Az utasításkódolás ellenőrzése:
# Célállomány: A64 ELF; ez elemzés, nem futtatás.
aarch64-linux-gnu-objdump -d -M no-aliases ./lab-cselAz aliasok nélkül a CSET helyén CSINC, a
CMP helyén nulla célregiszteres SUBS jelenhet
meg. Ez nem fordítási hiba.
LT helyett LO: kis
pozitív számokkal észrevétlen maradhat. Ellenpróba: legfelső bitjében
1-es operandus és kis pozitív érték összehasonlítása.MI használata előjeles kisebbként:
túlcsorduló kivonásnál hibás. A laborban N = 0 mellett LT
igaz.ADDS, SUBS vagy új CMP után a
kiválasztás már más művelet állapotát olvassa.CSEL első
forrása az igaz ág. Ellenőrizd külön az igaz és hamis esetet.W és X összehasonlítás
összekeverése: ugyanaz a regiszterpár más szélességen más
előjeles értéket jelenthet.Hibakereséskor először az összehasonlítás szélességét és operandusait nézd meg, utána az NZCV-t, végül a feltételkódot. Egyszerre csak egy feltételezett hibát módosíts.
(magyarázó, alapszint — cél:
megértés-ellenőrzés) 32 bites A64 CMP után
határozd meg az NZCV-t, valamint az LT és LO
igazságértékét:
0x00000000 és 0x00000000;0xffffffff és 0x00000001;0x7fffffff és 0xffffffff.(implementációs, középszint — cél: rutinépítés)
Egészítsd ki a labort az előjeles maximum W12-be és az
előjel nélküli maximum W13-ba választásával. A két utasítás
az első összehasonlítás jelzőit használja. Ellenőrizd GDB-vel a
lab_selected címkénél, majd vizsgáld meg az egyenlő
operandusok esetét is. Az eredeti önellenőrzés a rögzített laboradatokra
készült; más bemenetnél ehhez is igazítani kell.
(hibakeresési, középszint — cél: rutinépítés)
Szúrd be az A64 adds w14, w2, w2 utasítást közvetlenül az
első CMP után. Fordítsd újra, figyeld meg a kilépési
állapotot és az NZCV-t, majd egyetlen utasítás módosításával javítsd a
hibát.
1. megoldás — Segítség. Számítsd ki mindhárom
esetben a 32 bites kivonást bitminta szinten (nem előjeles
aritmetikával), és nézd meg, mikor kölcsönöz (C), mikor negatív a
legfelső bit (N), és mikor csordul túl előjelesen (V) — az
LT feltétele N≠V, az LO feltétele C=0.
1. megoldás — Teljes megoldás. A 32 bites kivonásokból levezetett eredmények:
| Operandusok | Kivonási bitminta | NZCV | LT |
LO |
|---|---|---|---|---|
0x00000000, 0x00000000 |
0x00000000 |
0, 1, 1, 0 | Hamis | Hamis |
0xffffffff, 0x00000001 |
0xfffffffe |
1, 0, 1, 0 | Igaz | Hamis |
0x7fffffff, 0xffffffff |
0x80000000 |
1, 0, 0, 1 | Hamis | Igaz |
A harmadik sorban előjelesen 2147483647 és −1 szerepel. A különbség túlcsordul, ezért N = 1 ellenére az első operandus nagyobb: N = V.
2. megoldás — Segítség. A maximum kiválasztása
pontosan a minimum feltételének a FORDÍTOTTJA — ha a minimumhoz
LT/LO kellett, a maximumhoz melyik feltételkód
adja a “nagyobb vagy egyenlő” döntést?
2. megoldás — Teljes megoldás. A következő
kiegészítés például közvetlenül a lab_selected címke elé
helyezhető:
// A64 ISA, AArch64; az eredeti CMP jelzőit használja.
csel w12, w1, w2, ge
csel w13, w1, w2, hsA laboradatokkal W12 = 1, W13 = 0x80000000.
Egyenlőségnél mindkét feltétel igaz, ezért az első forrást választják; a
két forrás számszerűen azonos.
3. megoldás — Segítség. Az ADDS is
módosítja az NZCV-t — mi történik az eredeti CMP jelzőivel,
mire a CSET/CSEL sorozat ténylegesen
kiértékelné őket?
3. megoldás — Teljes megoldás. A beszúrt összeadás
eredménye 2, jelzői N=0, Z=0, C=0, V=0. Emiatt LT hamis,
LO igaz lesz: W3 = 0, W4 = 1, a
minimumok felcserélődnek, W7 = 7. A várt hibás kilépési
állapot 1.
GDB-ben a lab_selected ponton a maszkolt NZCV várhatóan
nulla. Az objdump megmutatja a jelzőket felülíró
ADDS utasítást. Ha az összeadás jelzőire nincs szükség,
egyetlen javítás elegendő: ADDS helyett A64
ADD. Az eredmény továbbra is 2, de az eredeti
összehasonlítás jelzői megmaradnak.
Az A64 feltételes kiválasztása az NZCV állapotát alakítja adattá, nem új végrehajtási útvonallá. Ugyanaz a CMP előjeles és előjel nélküli döntést is megalapozhat, de a megfelelő feltételkódot nekünk kell kiválasztanunk. A CSET 0 vagy 1 értéket ad, a CSEL két regiszterérték egyikét másolja; egyik sem módosítja a jelzőket. Figyelni kell az összehasonlítás szélességére, a közbeékelt jelzőfrissítésre és a C operanduskiértékelési szabályaira. Az elágazás elhagyása nem automatikus gyorsítás: a forrásértékek előállításának költsége és az adatfüggőségek továbbra is számítanak. A laborban a regiszterek és az NZCV együttes ellenőrzése teszi láthatóvá a hibát.
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.
ConditionHolds, CSEL, CSINC,
valamint a CSET alias. Ellenőrizve: a link
él (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 ténylegesen fennmaradnak: a kódot itt nem fordítottuk és nem futtattuk. A kimenetek és regiszterállapotok levezetett, várt értékek. Az Ubuntu 24.04-ben telepített konkrét eszközverziókkal a buildet, a GDB regisztermegjelenítését és az aliasok megjelenítését a hallgatói laborban kell ellenőrizni. Teljesítménymérést nem végeztünk; a QEMU-futtatás nem igazol hardverhű ciklusszámot.