Egy előjel nélküli számláló növelésekor nemcsak az új érték érdekes, hanem az is, hogy a számláló átfordult-e nullára. Előjeles koordináták összeadásakor más kérdés merül fel: belefér-e a matematikai eredmény az ábrázolható tartományba? A két probléma ugyanahhoz az összeadóhoz kapcsolódik, de nem ugyanazt a jelzést igényli.
Az Egész aritmetika: ADD/SUB/MUL/DIV és a kettes komplemens viselkedése alfejezetben az eredmény alsó bitjeit vizsgáltuk. Most az eredmény mellett keletkező állapotjelzőket (condition flags) vezetjük le.
Architektúra és hatókör: az alfejezet valamennyi
utasítása, regisztere és assembly példája A64 ISA, AArch64,
ARMv8-A környezetre vonatkozik. A direktívák GNU assemblerhez,
AArch64 ELF-célhoz tartoznak; nem processzorutasítások. A labor
little-endian Linux-program, qemu-aarch64 user-mode céllal.
Cortex-M-szabályokat itt nem használunk.
Az A64 architekturális processzorállapotának, a PSTATE-nek négy jelzőjét együtt NZCV néven említjük. Egész összeadásnál és kivonásnál:
| A64-jelző | Név | Jelentés |
|---|---|---|
| N | negatív, negative | A műveleti szélesség szerinti eredmény legfelső bitje |
| Z | nulla, zero | 1, ha a teljes, adott szélességű eredmény nulla |
| C | átvitel, carry | Összeadásnál a legfelső biten túli átvitel; kivonásnál a kölcsönzés hiánya |
| V | előjeles túlcsordulás, overflow | 1, ha a matematikai előjeles eredmény nem ábrázolható az adott szélességen |
Az A64 ADDS és SUBS a számítás mellett
mind a négy jelzőt frissíti. Az A64 ADD és
SUB ezzel szemben változatlanul hagyja őket. Az
S tehát itt nem „signed”, hanem a jelzőfrissítésre
utal.
A műveleti szélességet az operandusok határozzák meg:
Wn operandusokkal 32 bites a művelet: az N jelző a
31. eredménybit.Xn operandusokkal 64 bites a művelet: az N jelző a
63. eredménybit.Az A64 Wn célregiszter írása továbbra is nullázza a
hozzá tartozó Xn felső 32 bitjét. Ettől azonban az N még
lehet 1: a jelzők a műveleti szélességet követik, nem a
nullakiterjesztett 64 bites tároló legfelső bitjét.
A jelzők nem hordoznak C-típust. Ugyanaz az összeadás egyszerre képezi az előjel nélküli értelmezéshez használható C-t és az előjeles értelmezéshez használható V-t.
Legyen a műveleti szélesség bitben, és pedig két bites operandus előjel nélküli, dimenzió nélküli értéke. A matematikai összeg és a tárolt eredmény:
Mivel , az összeg legfeljebb egy további bitet igényel:
Ez architekturális eredményszabály, nem állítás arról, milyen kapuhálózatot használ egy konkrét processzor.
A bitszintű levezetéshez legyen , és az operandusok, illetve az eredmény -edik bitje. A az adott bithelyre belépő átvitel; egyszerű összeadásnál :
A kimenő átvitel akkor 1, ha a három bemeneti bit közül legalább kettő 1. A végső C jelző .
Vizsgáljuk az alábbi 32 bites A64 műveletet:
0xffffffff, előjel nélkül
4294967295;0x00000001, előjel nélkül 1.| Bithely | Első operandus bitje | Második operandus bitje | Belépő átvitel | Eredménybit | Kilépő átvitel |
|---|---|---|---|---|---|
| 0 | 1 | 1 | 0 | 0 | 1 |
| 1–30, mindegyik külön | 1 | 0 | 1 | 0 | 1 |
| 31 | 1 | 0 | 1 | 0 | 1 |
Az átvitel végighalad az összes bithelyen. A matematikai összeg
0x100000000, de a 32 bites eredmény
0x00000000. Ezért:
Előjeles kettes komplemens olvasatban viszont ugyanez −1 + 1
= 0, ami ábrázolható. Így V = 0, a teljes NZCV pedig
0b0110.
A carry nem azonos az előjeles túlcsordulással.
bites kettes komplemens ábrázolásban az előjeles tartomány:
Különböző előjelű számok összeadásakor az eredmény ebben a tartományban marad. Túlcsordulás akkor lehetséges, ha:
Legyen , és a két operandus, illetve az eredmény előjelbitje. Ekkor összeadásra:
Konkrét 32 bites A64 példa:
0x7fffffff: előjelesen 2147483647;0x00000001: előjelesen 1;0x80000000, előjelesen
−2147483648.A matematikai eredmény 2147483648 lenne, amely nem fér el előjeles 32 biten.
Bitszinten a 0. bithelyen átvitel keletkezik, amely végighalad az 1–30. bithelyeken. Az előjelbithez tehát érkezik. Ott viszont:
így az eredmény előjelbitje 1, a kilépő átvitel pedig . Következésképpen C = 0, miközben V = 1.
Az overflow másik, ezzel egyenértékű alakja:
Az indoklás az előjelbit összeadásából következik:
Az előző carry-példában , tehát V = 0. A mostani példában különböznek, ezért V = 1.
Az alábbi elméleti, ISA-szemantikából levezetett, 32 bites A64 eredmények mind a négy C/V-kombinációt megmutatják:
| Operandusok bitmintája | Eredmény bitmintája | C | V | NZCV |
|---|---|---|---|---|
0x00000001 + 0x00000001 |
0x00000002 |
0 | 0 | 0b0000 |
0xffffffff + 0x00000001 |
0x00000000 |
1 | 0 | 0b0110 |
0x7fffffff + 0x00000001 |
0x80000000 |
0 | 1 | 0b1001 |
0x80000000 + 0x80000000 |
0x00000000 |
1 | 1 | 0b0111 |
Az utolsó sor előjeles olvasata −2147483648 + −2147483648: a matematikai eredmény túl kicsi. Előjel nélkül ugyanakkor az összeg pontosan , ezért carry is keletkezik.
C-ben az előjel nélküli összeadás moduláris viselkedése meghatározott. A túlcsorduló előjeles összeadás viszont nem definiált viselkedés: egy C-programban nem szabad előbb túlcsorduló előjeles összeadást végezni, majd az eredmény előjeléből biztonságot ellenőrizni. Az ISA szabálya nem írja felül a C nyelv szabályát.
Az A64 SUBS is frissíti az NZCV-t. Kivonáskor azonban C
= 1 azt jelenti, hogy nem kellett kölcsönözni (no
borrow).
Ennek levezetéséhez az bitre korlátozott bitenkénti komplementer értéke . Az összeadóval végzett kivonás:
Ez akkor éri el értékét, ha . Következésképpen, előjel nélküli olvasatban:
A 32 bites 0 − 1 eredménye 0xffffffff, C =
0, V = 0. A 0 − 0 eredménye nulla, C = 1.
Kivonásnál az előjeles overflow feltétele:
Itt eltérő előjelű operandusoknál lehet túlcsordulás. Az N önmagában ezért nem általános előjeles összehasonlítási eredmény; a feltételkódok ezt a V figyelembevételével kezelik majd a Feltételes végrehajtás: CSET/CSEL és feltételkódok alfejezetben.
Az A64 CMP alias, nem külön
összehasonlító számítás:
// A64 ISA: a két sor azonos utasításkódolást jelent.
CMP W1, W2
SUBS WZR, W1, W2Az A64 WZR nullaregiszterbe írt eredmény elvész, de a
jelzők frissülnek. A CMP nem módosítja az operandusait. Az
A64 64 bites változatában Xn operandusok és
XZR szerepelnek.
Immediate-korlát: az immediate-os
ADDS/SUBS és a CMP ugyanazt a
mezőt használja, mint az ADD/SUB (lásd az A64 immediate-korlátok összefoglalóját);
tetszőleges 32 vagy 64 bites konstans közvetlenül nem írható
beléjük.
A könyv további alfejezetei erre a táblázatra hivatkoznak, ha az
NZCV-t kell követni. A szabály egyszerű: az A64-ben csak a
kifejezetten jelzőállító utasítások írják az NZCV-t — a legtöbb
ilyen nevének S a végződése, illetve összehasonlító alias
(CMP, CMN, TST). Minden más
utasítás változatlanul hagyja, akkor is, ha az eredménye nulla vagy
negatív.
| Csoport | A64-utasítások | Hatás az NZCV-re |
|---|---|---|
| Aritmetika jelzőállítással | ADDS, SUBS, ADCS,
SBCS, NEGS, NGCS; aliasok:
CMP (= SUBS nullaregiszterbe),
CMN (= ADDS nullaregiszterbe) |
mind a négy jelzőt írja az eredmény szerint |
| Logika jelzőállítással | ANDS, BICS; alias: TST (=
ANDS nullaregiszterbe) |
N és Z az eredmény szerint; C = 0, V = 0 |
| Feltételes összehasonlítás | CCMP, CCMN |
ha a feltétel teljesül, az összehasonlítás szerint; különben az
utasításban megadott #nzcv értéket írja |
| Lebegőpontos összehasonlítás | FCMP, FCMPE |
az összehasonlítás szerint (rendezetlen eredménynél
0011); lásd a skaláris lebegőpontos
utasításokat |
| Közvetlen írás | MSR NZCV, Xn |
a regiszter 31–28. bitjét írja be |
| Nem írja | ADD, SUB, ADC,
SBC, NEG, MUL, MADD,
MSUB, SDIV, UDIV,
AND, ORR, EOR, BIC,
MVN,
LSL/LSR/ASR/ROR,
MOV/MOVZ/MOVK/MOVN,
bitfield-utasítások, LDR/STR és társaik,
ADR/ADRP, B, BL,
BR, BLR, RET,
CBZ/CBNZ,
TBZ/TBNZ |
változatlanul hagyja |
| Olvassa, de nem írja | B.cond, CSEL, CSET,
CSINC, CSINV, CSNEG,
ADC, SBC (a C jelzőt) |
a jelzőkből dönt vagy számol, de nem módosítja őket |
Két következmény, amelyre a későbbi alfejezetek építenek:
CBZ/CBNZ és a
TBZ/TBNZ jelzők nélkül
teszteli a regisztert, ezért két NZCV-t használó utasítás közé is
beilleszthető (Feltétel nélküli és feltételes
elágazás).ADDS/SUBS és a hozzá tartozó
B.cond vagy CSEL közé tett nem jelzőállító
utasítás (például ADD, MOV, LDR)
nem rontja el a jelzőket; egy közbeiktatott CMP vagy
ANDS viszont felülírja őket.Munkakönyvtár: ~/arm-labor/a64/a016/ —
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/a016
cd ~/arm-labor/a64/a016A program minden vizsgált művelet után kiolvassa a jelzőket. Az A64
MRS Xn, NZCV az NZCV rendszerregiszteren keresztül másolja
őket általános célú regiszterbe, és maga nem módosítja őket.
A64 NZCV kiolvasott bithelye |
Tartalom |
|---|---|
| 31 | N |
| 30 | Z |
| 29 | C |
| 28 | V |
| Többi bit | 0 |
Így például az NZCV = 0b0110 kiolvasott értéke
0x0000000060000000, nem 0x6. Ezek
regiszterértékek, nem memóriacímek.
Mentsük a következő teljes forrást lab-nzcv.s néven:
// A64 ISA, ARMv8-A; GNU as, AArch64 Linux ELF.
.text
.global _start
.global lab_inspect
.type _start, %function
_start:
// A64 A: 0xffffffff + 1, 32 bit.
MOVZ W0, #0xffff
MOVK W0, #0xffff, LSL #16
MOVZ W1, #1
ADDS W2, W0, W1
ADD W9, W1, W1 // Nem modosítja az NZCV-t.
MRS X10, NZCV
// A64 B: 0x7fffffff + 1, 32 bit.
MOVZ W0, #0xffff
MOVK W0, #0x7fff, LSL #16
ADDS W3, W0, W1
MRS X11, NZCV
// A64 C: 0x80000000 + 0x80000000, 32 bit.
MOVZ W0, #0x8000, LSL #16
ADDS W4, W0, W0
MRS X12, NZCV
// A64 D: 1 + 1, 32 bit.
ADDS W5, W1, W1
MRS X13, NZCV
// A64 E: 0 - 1, 32 bit.
SUBS W6, WZR, W1
MRS X14, NZCV
// A64 F: osszehasonlitas; W1 erteke nem valtozik.
CMP W1, W1
MRS X15, NZCV
lab_inspect:
// A64 Linux exit(0).
MOVZ W0, #0
MOVZ W8, #93
SVC #0
.size _start, .-_start
.section .note.GNU-stack,"",%progbitsA konstansépítő MOVZ/MOVK és az
SVC #0 immediate-jai kódolhatók (lásd az A64 immediate-korlátok összefoglalóját).
Build és futtatás — target: AArch64 Linux, qemu-user; host: Ubuntu 24.04 x86_64.
aarch64-linux-gnu-as -g -march=armv8-a -EL \
-o lab-nzcv.o lab-nzcv.s
aarch64-linux-gnu-ld -EL -e _start \
-o lab-nzcv lab-nzcv.o
aarch64-linux-gnu-objdump -d -M no-aliases lab-nzcv
qemu-aarch64 ./lab-nzcv
printf 'exit=%d\n' "$?"Várt, nem itt mért eredmény: a program nem ír a
szabványos kimenetre; a shell exit=0 sort jelez. Ez csak a
szabályos befejezést ellenőrzi, nem a jelzők
helyességét.
Az aliasok nélküli disassemblyben az A64 CMP W1, W1
helyén subs wzr, w1, w1 alak várható.
A regiszterellenőrzéshez az első terminálban indítsuk újra a qemu-user célprogramot:
qemu-aarch64 -g 1234 ./lab-nzcv
printf 'exit=%d\n' "$?"Második terminálban csatlakozzunk:
gdb-multiarch -q ./lab-nzcv \
-ex 'target remote localhost:1234' \
-ex 'break lab_inspect' \
-ex 'continue' \
-ex 'info registers x2 x3 x4 x5 x6 x9' \
-ex 'info registers x10 x11 x12 x13 x14 x15'A töréspontban a következő levezetett, várt A64 állapot ellenőrizendő:
| Eset | Eredmény 32 bites nézetben | NZCV | Pillanatfelvétel |
|---|---|---|---|
| A | W2 = 0x00000000 |
0b0110 |
X10 = 0x0000000060000000 |
| B | W3 = 0x80000000 |
0b1001 |
X11 = 0x0000000090000000 |
| C | W4 = 0x00000000 |
0b0111 |
X12 = 0x0000000070000000 |
| D | W5 = 0x00000002 |
0b0000 |
X13 = 0x0000000000000000 |
| E | W6 = 0xffffffff |
0b1000 |
X14 = 0x0000000080000000 |
| F | Nincs tárolt kivonási eredmény | 0b0110 |
X15 = 0x0000000060000000 |
Az A64 X2–X6 felső 32 bitje nulla.
Különösen: X3 = 0x0000000080000000 mellett a B eset N
jelzője mégis 1. Az A64 W9 = 2, de a közbeiktatott
ADD nem írta felül az A eset jelzőit.
A GDB-ben kiadott continue után a program várt kilépési
állapota 0.
| Tünet | Hipotézis | Adatgyűjtés és egyetlen változtatás |
|---|---|---|
| Az eredmény helyes, a jelző régi | A64 ADD szerepel ADDS helyett |
objdump segítségével ellenőrizzük a kódot; csak a
mnemonicot javítsuk |
| A várt 32 bites átfordulás elmarad | A64 Xn operandusokkal számoltunk |
Ellenőrizzük az utasítás szélességét; csak a hibás összeadást módosítsuk |
| A kivonás C-je „fordítottnak” látszik | A kölcsönzést és annak hiányát összekevertük | Vessük össze a 0 − 1 és 0 − 0 esetet; a
kód helyett először az elvárást javítsuk |
| Több eredményhez ugyanaz a jelző tartozik | A kiolvasás előtt egy későbbi művelet frissítette az NZCV-t | Helyezzük az A64 MRS utasítást közvetlenül a vizsgált
művelet után |
A jelzők egyetlen közös, felülírható állapotot alkotnak; nem kapcsolódik minden eredményregiszterhez külön NZCV. Hibakereséskor ezért egyszerre ellenőrizzük az operandusokat, a szélességet, az eredményt és a kiolvasás helyét.
(magyarázó, alapszint — cél:
megértés-ellenőrzés) Határozd meg a 32 bites A64
0x80000000 + 0xffffffff eredményét és NZCV-jét. Külön
indokold C és V értékét előjel nélküli, illetve előjeles
értelmezéssel.
(implementációs, középszint — cél:
megértés-ellenőrzés) A labor első összeadását módosítsd
egyetlen sorban 64 bitesre, ADDS X2, X0, X1 alakra. A
konstansépítést hagyd változatlanul. A fenti qemu-user builddel és
GDB-ellenőrzéssel vizsgáld meg X2, annak alsó 32 bites
W2 nézete, valamint X10 értékét. Miért nem
következik a nulla W2 értékből Z = 1?
(hibakeresési, középszint — cél: rutinépítés) Az
eredeti program B esetében szándékosan cseréld az A64
ADDS W3, W0, W1 utasítást ADD W3, W0, W1
alakra. Előre jelezd meg az eltérő regiszterértéket, majd
objdump és GDB segítségével igazold a hipotézist. Egyetlen
módosítással javítsd vissza.
1. megoldás — Segítség. Számítsd ki az előjel nélküli összeget binárisan (hány bitre nő), majd külön az előjeles összeget — fér-e a 32 bites előjeles tartományba?
1. megoldás — Teljes megoldás. Eredmény:
0x7fffffff; NZCV: 0b0011. Előjel
nélkül az összeg 0x17fffffff, ezért a 32. biten átvitel
keletkezik: C = 1. Előjelesen −2147483648 + −1 adódik, ami kisebb a
minimumértéknél: V = 1. A tárolt eredmény előjelbitje 0, és nem nulla,
ezért N = 0, Z = 0.
2. megoldás — Segítség. Nézd meg, mit csinál a
konstansépítő MOVZ W0, .../MOVZ W1, ... pár az
X0/X1 FELSŐ 32 bitjével, mielőtt egyáltalán az
ADDS-hoz érnél.
2. megoldás — Teljes megoldás.
X2 = 0x0000000100000000, W2 = 0,
X10 = 0. A konstansépítés W0 és
W1 írásával már nullázta az A64 X0 és
X1 felső felét. A 64 bites összeadás ezért 4294967295 + 1
értéket számít, amely mindkét 64 bites értelmezésben ábrázolható. Nincs
carry és overflow. Z a teljes 64 bites eredményt vizsgálja, amely nem
nulla; az alsó 32 bites nézet önmagában nem döntő.
3. megoldás — Segítség. Ha az ADD nem
frissíti az NZCV-t, melyik KORÁBBI eset jelzői maradnak érvényben az
MRS X11, NZCV pillanatában?
3. megoldás — Teljes megoldás. Az A64
W3 továbbra is 0x80000000, de
X11 = 0x0000000060000000 lesz. A hibás ADD nem
frissíti az NZCV-t, így a pillanatfelvétel az A eset 0b0110
jelzőit őrzi. A közbeeső konstansépítés sem változtatott rajtuk. Az
ADDS visszaállítása után a várt
X11 = 0x0000000090000000. A helyes eredmény tehát önmagában
nem igazolja a helyes jelzőkezelést.
Az A64 aritmetikában az eredmény és a jelzők együtt írják le, mi
történt az adott műveleti szélességen. Az N az eredmény legfelső bitje,
a Z a teljes eredmény nullaságát jelzi. Az összeadás C jelzője az előjel
nélküli tartományból kilépést, V jelzője az előjeles tartomány
megsértését mutatja; egyikből sem következik a másik. A kivonás C-je a
kölcsönzés hiányát jelenti. Az A64 CMP eldobott eredményű
SUBS, a jelzőket viszont megtartja. Az NZCV-t csak a
jelzőállító utasítások írják (…S végződés,
CMP, CMN, TST, CCMP,
FCMP); a teljes áttekintést az alfejezet összefoglaló
táblázata adja. A laborban közvetlen kiolvasással ellenőriztük a
levezetett állapotokat.
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.
AddWithCarry művelet, az ADDS,
SUBS, CMP, valamint az NZCV
leírása. Ellenőrizve: a link él (a
developer.arm.com → support.arm.com
átirányítás megerősített); 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 forrást itt nem fordítottuk és nem futtattuk. Minden közölt eredmény ISA-szemantikából levezetett, várt állapot. Az Ubuntu 24.04 helyben telepített csomagverzióit, a disassembly konkrét megjelenését és a QEMU–GDB kapcsolat működését a laborban kell ellenőrizni. Teljesítményadatot nem közöltünk; a bitenkénti levezetés nem mikroarchitekturális időzítési modell.