9.1 Logikai alapműveletek és bitmaszkolás

9.1.1 Miért fontos a megfelelő maszk?

Egy fájlformátum állapotmezőjében néhány bit jelölheti az érvényességet, mások a feldolgozási módot. A feladat gyakran nem a teljes érték lecserélése, hanem kijelölt bitek kiolvasása, beállítása vagy törlése, miközben minden más bit változatlan marad. Ugyanez jelenik meg jogosultságjelzők, tömörített adatok és protokollmezők kezelésénél.

A bitmaszk (bit mask) olyan bitminta, amely megadja, hogy egy bitenkénti művelet mely pozíciókat érintse. A maszk jelentése a művelettől függ: az egyik művelet megtartja, a másik beállítja, a harmadik törli a maszkkal kijelölt biteket.

Ebben az alfejezetben minden utasítás és regiszternév az A64 ISA, AArch64, ARMv8-A alapprofil része. Az assemblyblokkok GNU as AArch64-szintaxist használnak; a direktívák assemblerutasítások, nem processzorutasítások. A labor little-endian Linux-program, célja qemu-aarch64.

Az előfeltétel a feltételkódok és feltételes kiválasztás ismerete. A példák előjel nélküli, 32 bites bitmintákkal dolgoznak. A bitenkénti műveletek maguk nem értelmeznek előjelet: ugyanazokon a biteken ugyanazt végzik.

9.1.2 Négy művelet, négy maszkolási szándék

Legyen az adat x, a maszk m, mindkettő uint32_t érték. Az A64 ISA regiszteres alakjai:

Szándék C-kifejezés A64 ISA-művelet Az eredmény jelentése
Elkülönítés x & m AND Wd, Wn, Wm Csak a maszk 1-es helyein maradhat 1
Beállítás x \| m ORR Wd, Wn, Wm A maszk 1-es helyein biztosan 1 lesz
Átváltás x ^ m EOR Wd, Wn, Wm A maszk 1-es helyein megfordul a bit
Törlés x & ~m BIC Wd, Wn, Wm A maszk 1-es helyein biztosan 0 lesz

A táblázatban az A64 Wd a cél, Wn az adatforrás, Wm a maszkforrás. A cél megegyezhet valamelyik forrással: az utasítás az eredeti forrásértékekből számítja ki az eredményt.

Egyetlen bitre az igazságtábla:

Adatbit Maszkbit AND OR XOR AND NOT
0 0 0 0 0 0
0 1 0 1 1 0
1 0 0 1 1 1
1 1 1 1 0 0

Az A64 ISA itt használt AND, ORR, EOR és BIC alakjai nem módosítják az NZCV állapotjelzőket. Egy utánuk következő feltételes döntés ezért nem automatikusan a maszkolás eredményére vonatkozik. A labor külön összehasonlítással ellenőrzi az eredményeket.

9.1.2.1 Elkülönítés és törlés

Az A64 AND kétféleképpen is megfogalmazhat egy feladatot:

Például az előjel nélküli 0x000000AC érték alsó négy bitjének elkülönítése:

𝟶𝚡𝟶𝟶𝟶𝟶𝟶𝟶𝙰𝙲&𝟶𝚡𝟶𝟶𝟶𝟶𝟶𝟶𝟶𝙵=𝟶𝚡𝟶𝟶𝟶𝟶𝟶𝟶𝟶𝙲. \texttt{0x000000AC}\ \mathbin{\&}\ \texttt{0x0000000F} = \texttt{0x0000000C}.

A 0-tól számozott 3. bit törlése ugyanezen a 32 bites értéken:

𝟶𝚡𝟶𝟶𝟶𝟶𝟶𝟶𝙰𝙲&𝟶𝚡𝙵𝙵𝙵𝙵𝙵𝙵𝙵𝟽=𝟶𝚡𝟶𝟶𝟶𝟶𝟶𝟶𝙰𝟺. \texttt{0x000000AC}\ \mathbin{\&}\ \texttt{0xFFFFFFF7} = \texttt{0x000000A4}.

Az A64 BIC ugyanezt a törlést a törlendő biteket kijelölő 0x00000008 maszkkal fejezi ki. A komplementálás a műveleti szélességen belül történik: 32 bites műveletben a maszk 32 bitje fordul meg.

9.1.2.2 Beállítás és átváltás

Az A64 ORR beállító művelete idempotens: ugyanazt a maszkot ismét alkalmazva az eredmény nem változik tovább. Az A64 EOR ezzel szemben átváltást (toggle) végez: ugyanazzal a maszkkal kétszer alkalmazva visszaadja az eredeti értéket.

Ez mérnöki szempontból lényeges különbség. Az „érvényes bit legyen 1” feladatra beállítás kell, nem átváltás. Az utóbbi egy már érvényes értéket érvénytelenné tenne.

9.1.3 Miért nem írható minden maszk közvetlenül az utasításba?

Az A64 utasítások 32 bitesek. Az opcode, a regiszterazonosítók és más vezérlőmezők mellett nem fér el bennük tetszőleges 32 vagy 64 bites konstans.

A közvetlen operandus (immediate operand) ezért nem feltétlenül a teljes számot tárolja. Az A64 logikai immediate egy szabályos bitminta tömör leírása. Kódolása az N, immr és imms mezőket használja, rendre 1, 6 és 6 biten; nem minden mezőkombináció érvényes.

A megengedett minták előállításának gondolatmenete:

  1. Válassz egy elemhosszt: 2, 4, 8, 16, 32 vagy 64 bitet.
  2. Az elemen belül legyen egy összefüggő 1-es sorozat, a többi bit pedig 0.
  3. Az 1-es sorozat hossza legalább 1, de kisebb az elemhossznál.
  4. Az elemet körkörösen el lehet forgatni.
  5. Az így kapott elemet ismételni kell a teljes műveleti szélesség kitöltéséig.

A64 32 bites műveletben legfeljebb 32 bites elem választható, és az N mező 0. A64 64 bites műveletben a 64 bites elem is használható.

Az elforgatás miatt az 1-es sorozat „átérhet” az elem végéről az elejére. A teljesen nulla és a teljesen egyes maszk viszont nem kódolható logikai immediate-ként.

Előjel nélküli, 32 bites maszk Kódolható? Indok
0x0000000F Igen 32 bites elemben négy összefüggő 1
0xFFFFFFF7 Igen Körkörösen nézve 31 összefüggő 1
0x00FF00FF Igen A 0x00FF 16 bites elem ismétlődik
0x55555555 Igen A 0b01 2 bites elem ismétlődik
0x80000001 Igen Két szomszédos 1 az elemhatáron át
0x00000005 Nem Két különálló 1, megfelelő ismétlődés nélkül
0x00000000 Nem Nincs 1-es sorozat
0xFFFFFFFF Nem Nincs 0 az elemben

A korlát tehát nem a szám nagyságáról szól: az 5 nem használható ilyen immediate-ként, miközben a sokkal nagyobb 0x55555555 igen.

A szélesség is számít. A 0x00FF00FF 32 bites maszk kódolható, de a 0x0000000000FF00FF 64 bites maszk nem: a 16 bites elem nem ismétlődik végig, és a teljes 64 bites mintában sincs egyetlen összefüggő 1-es sorozat.

Az A64 AND, ORR és EOR rendelkezik ilyen immediate-alakkal. Önálló BIC immediate-utasításkódolás nincs: konstans bitek törléséhez használható a komplementer maszkkal végzett AND, vagy a maszk regiszterbe töltése és a regiszteres BIC. Az esetleges assembler-rövidítéseket nem szabad külön ISA-kódolással összetéveszteni.

9.1.4 Kidolgozott példa: önellenőrző maszkolási labor

Munkakönyvtár: ~/arm-labor/a64/a032/ — 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/a032
cd ~/arm-labor/a64/a032

A következő teljes program külön célregiszterekbe számolja az eredményeket, majd összehasonlítja őket a várt értékekkel. Nem ír ki szöveget: siker esetén 0, eltéréskor 1 a kilépési állapot.

Az A64 példában szereplő további konstanskorlátok:

A GNU AArch64 .p2align 2 direktíva 22=42^2=4 bájtos igazítást kér.

A fájl neve legyen lab-mask.s:

// A64 ISA, ARMv8-A; GNU as AArch64; little-endian Linux.
.arch armv8-a
.text
.p2align 2
.global _start
.type _start, %function

_start:
    movz    w0, #0xAC

    and     w1, w0, #0x0F
    orr     w2, w0, #0x01
    eor     w3, w0, #0x08

    movz    w4, #0x08
    bic     w5, w0, w4

    // A 0x05 nem logikai immediate: regiszteres maszk kell.
    movz    w6, #0x05
    and     w7, w0, w6

    // XOR-alapú csere két különböző regiszteren.
    movz    w10, #0x12
    movz    w11, #0xA5
    eor     w10, w10, w11
    eor     w11, w10, w11
    eor     w10, w10, w11

lab_observe:
    cmp     w1, #0x0C
    b.ne    lab_fail
    cmp     w2, #0xAD
    b.ne    lab_fail
    cmp     w3, #0xA4
    b.ne    lab_fail
    cmp     w5, #0xA4
    b.ne    lab_fail
    cmp     w7, #0x04
    b.ne    lab_fail
    cmp     w10, #0xA5
    b.ne    lab_fail
    cmp     w11, #0x12
    b.ne    lab_fail

    movz    w0, #0
    b       lab_exit

lab_fail:
    movz    w0, #1

lab_exit:
    movz    x8, #93
    svc     #0

.size _start, .-_start
.section .note.GNU-stack, "", @progbits

A 0x05 előállítására használt A64 MOVZ más konstanskódolást használ, mint a logikai utasítások: ezért fogadja el azt, amit az AND immediate-alakja nem.

Az assemble–link–run lépéssor Ubuntu 24.04 x86_64 hoston:

# Target: qemu-user, AArch64 Linux, little-endian.
aarch64-linux-gnu-as -EL -march=armv8-a \
    -o lab-mask.o lab-mask.s
aarch64-linux-gnu-ld -EL -e _start \
    -o lab-mask.elf lab-mask.o
qemu-aarch64 ./lab-mask.elf
printf 'kilepesi_allapot=%d\n' "$?"

# Target: ugyanennek az AArch64 ELF-nek az elemzése, nem futtatás.
aarch64-linux-gnu-objdump -d lab-mask.elf

Levezetett, nem ténylegesen futtatott kimenet: kilepesi_allapot=0.

A lab_observe címkénél várt állapot:

A64 regiszternézet Várt, előjel nélküli érték Jelentés
W0 0x000000AC Eredeti adat
W1 0x0000000C Alsó négy bit
W2 0x000000AD 0. bit beállítva
W3 0x000000A4 3. bit átváltva
W5 0x000000A4 3. bit törölve
W7 0x00000004 A 0. és 2. bit elkülönítve
W10 0x000000A5 Csere utáni első érték
W11 0x00000012 Csere utáni második érték

A64-en a Wn célregiszterbe írás nullázza a megfelelő Xn felső 32 bitjét. Ez nem a maszk különleges tulajdonsága, hanem a 32 bites regiszterírás általános szabálya.

9.1.5 Az XOR-alapú csere: levezetés és korlát

Legyen két különböző A64 regiszter kezdeti bitmintája aa és bb. Az XOR tulajdonságai: x⊕x=0x\oplus x=0, illetve x⊕0=xx\oplus0=x.

A labor három lépése:

  1. Az első regiszterbe a⊕ba\oplus b kerül.
  2. A másodikba (a⊕b)⊕b=a(a\oplus b)\oplus b=a kerül.
  3. Az elsőbe (a⊕b)⊕a=b(a\oplus b)\oplus a=b kerül.

A csere tehát ideiglenes regiszter nélkül működik, ha a két regiszter különböző. Ha ugyanazt a regisztert adjuk meg mindkét operandusként, az első XOR lenullázza az értéket. Két különböző regiszter azonos tartalma viszont nem hiba.

Ez oktatási azonosság, nem általános optimalizálási ajánlás. A három A64 EOR egymástól függő művelet; az ideiglenes regiszteres megoldás rendszerint világosabb. Pusztán az utasításszámból nem következik ciklusszám vagy gyorsulás, QEMU alatt pedig nem mérhető megbízhatóan egy fizikai processzor ilyen késleltetése.

9.1.6 Tipikus hibák és ellenpróbák

A hibakeresés menete: tünet → hipotézis → adatgyűjtés → egy változtatás → ellenőrzés.

Tünet Hipotézis Ellenpróba
A fordítás elutasít egy kicsi maszkot Nem logikai immediate-minta Töltsd regiszterbe, és csak az operandusalakot cseréld
Második alkalmazásra eltűnik egy jelző Beállítás helyett átváltás történt Ugyanazzal a maszkkal kétszer futtasd
Nem a kívánt bit törlődik A megtartó és törlő maszk összekeveredett Írd fel a maszkot binárisan
A feltételes döntés hibás Régi NZCV alapján dönt Vizsgáld meg az utolsó jelzőfrissítő utasítást
Eltűnik a 64 bites érték felső fele 32 bites célregisztert használtál Ellenőrizd a teljes Xn nézetet

A laborállapot megfigyeléséhez az első terminálban:

# Target: qemu-user, AArch64; GDB-kapcsolatra vár.
qemu-aarch64 -g 1234 ./lab-mask.elf

A második terminálban:

# Target: a fenti qemu-user folyamat távoli hibakeresése.
gdb-multiarch -q ./lab-mask.elf \
    -ex 'target remote localhost:1234' \
    -ex 'break lab_observe' \
    -ex 'continue' \
    -ex 'info registers x0 x1 x2 x3 x4 x5 x6 x7 x10 x11 cpsr'

A GDB itt a teljes A64 Xn nézeteket mutatja; a táblázat értékei ezek alsó 32 bitjében várhatók, nulla felső résszel. A continue debuggerparancs után a helyes program várhatóan normálisan, 0 állapottal lép ki.

Különösen hasznos ellenpróba a kezdőérték módosítása 0xA4-re. Ekkor az átváltás eredménye 0xAC, a törlésé továbbra is 0xA4. A két művelet csak az eredeti adat miatt adott azonos eredményt.

9.1.7 Feladatok

  1. (magyarázó, alapszint — cél: megértés-ellenőrzés) Melyik művelet kell a 6. bit biztos beállításához, biztos törléséhez és átváltásához? Miért kódolható a 32 bites 0x80000001, de nem kódolható a 0x00000005 logikai immediate-ként?

  2. (implementációs, középszint — cél: rutinépítés) Bővítsd a labort úgy, hogy az eredeti adat 0. és 2. bitjét beállítsa, a 7. bitjét törölje, majd a 3. bitjét átváltsa. Az eredmény legyen az A64 W13 regiszterben, alsó négy bitje pedig W14-ben. Használj regiszteres maszkot ott, ahol szükséges, és bővítsd az önellenőrzést.

  3. (hibakeresési, középszint — cél: rutinépítés) Készíts lab-mask-bad.s nevű másolatot. Cseréld az A64 and w7, w0, w6 sort and w7, w0, #0x05 alakra. Rögzítsd az assembler kilépési állapotát és diagnosztikáját. Miért nem futásidejű hiba ez? Javítsd egyetlen operandus visszacserélésével, majd ellenőrizd a programot.

9.1.8 Megoldások

1. megoldás — Segítség. A 6. bit maszkja 262^6; nézd meg az igazságtáblát, hogy a négy művelet közül melyik GARANTÁLJA az 1-et, melyik a 0-t, melyik csak megfordítja. A kódolhatósághoz: fogalmazd meg mindkét mintát mint “egy elem körkörös elforgatva, ismételve”.

1. megoldás — Teljes megoldás. A64-en a 6. bit maszkja 0x40: beállításhoz ORR, átváltáshoz EOR használható. Törléshez BIC alkalmazható regiszterben tartott 0x40 maszkkal, vagy AND a 32 bites 0xFFFFFFBF immediate-maszkkal.

A 0x80000001 két 1-ese körkörösen szomszédos. A 0x00000005 1-esei nem szomszédosak, és kisebb elem ismétlésével sem állítható elő a teljes bitminta.

2. megoldás — Segítség. Haladj lépésről lépésre a 0xAC kezdőértéktől: először a beállítás (ORR), majd a törlés (itt a 0x05 maszk sem logikai immediate, szükség lehet regiszteres alakra vagy a komplementer immediate-re), végül az átváltás (EOR) — mindig az ELŐZŐ lépés eredményére építve.

2. megoldás — Teljes megoldás. A lab_observe címke elé beilleszthető:

// A64 ISA; kiegészítés a teljes lab-mask.s programhoz.
movz    w12, #0x05
orr     w13, w0, w12
and     w13, w13, #0xFFFFFF7F
eor     w13, w13, #0x08
and     w14, w13, #0x0F

A levezetett értéksor: 0xAC → 0xAD → 0x2D → 0x25. Az alsó négy bit 0x05. A sikeres kilépést előkészítő sor elé kerüljenek az ellenőrzések:

// A64 ISA; további ellenőrzések a meglévő lab_fail ággal.
cmp     w13, #0x25
b.ne    lab_fail
cmp     w14, #0x05
b.ne    lab_fail

Az eredeti assemble–link–run parancsokat újrafuttatva ismét 0 kilépési állapot várható.

3. megoldás — Segítség. A 0x05 ugyanaz az érték, amiről a fejezet elején már megállapítottuk, hogy nem logikai immediate — az assembler ezt mikor veszi észre: fordításkor vagy futáskor?

3. megoldás — Teljes megoldás. A hibás változat összeszerelési kísérlete:

# Target: qemu-userhez készülő AArch64 objektum.
# Szándékos fordítási hiba; nem követi linkelés vagy futtatás.
aarch64-linux-gnu-as -EL -march=armv8-a \
    -o lab-mask-bad.o lab-mask-bad.s
printf 'assembler_allapot=%d\n' "$?"

Nem nulla assemblerállapot és az immediate érvénytelenségére utaló diagnosztika várható; pontos szövege verziófüggő. A hibás sorhoz nem készíthető megfelelő utasításkód, ezért ez összeszerelési, nem futásidejű hiba.

Az operandus visszacserélése után:

# Target: qemu-user, AArch64 Linux.
aarch64-linux-gnu-as -EL -march=armv8-a \
    -o lab-mask-bad.o lab-mask-bad.s
aarch64-linux-gnu-ld -EL -e _start \
    -o lab-mask-bad.elf lab-mask-bad.o
qemu-aarch64 ./lab-mask-bad.elf
printf 'kilepesi_allapot=%d\n' "$?"

A várt kilépési állapot 0. Az adat és a maszk nem változott; csak a maszk átadásának kódolható módját állítottuk vissza.

9.1.9 Rövid összefoglaló

A64-ben az AND megtartja a maszkkal kijelölt biteket, az ORR beállítja, az EOR átváltja, a BIC pedig törli őket. A helyes választást a kívánt állapotváltozás határozza meg, nem az, hogy egyetlen próbaadaton melyik ad megfelelő eredményt. A logikai immediate csak szabályos, elforgatott és szükség esetén ismételt bitmintát írhat le; egy kis szám sem feltétlenül kódolható. Ilyenkor regiszteres maszk használható. A műveleti szélességet mindig ellenőrizni kell, és az eredményvizsgálat nem támaszkodhat véletlenül megmaradt állapotjelzőkre. Az XOR-csere hasznos azonosság, de nem automatikus gyorsítás.

9.1.10 Források és ellenőrzési hiányok

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.

Ellenőrzési hiányok, amelyek a fenti ellenőrzés után is ténylegesen fennmaradnak: a példák nem kerültek futtatásra. Az Ubuntu 24.04 helyi csomagverzióit, az assembler pontos diagnosztikáját, a GDB-kapcsolatot és a levezetett kilépési állapotokat a hallgató laborjában kell ellenőrizni. Teljesítménymérés vagy mért ciklusszám nem szerepel az alfejezetben.