A feltételes utasítások fogalma mutatja be a jcc-t, a feltételen alapuló elágazásra szolgáló utasítások családját.
A jcc csak akkor adja át a vezérlést a program egy másik pontjára, ha a feltétele teljesül.
A modern CPU-k azonban gyorsak, és sok utasítást képesek párhuzamosan végrehajtani. Túl költséges lenne leállítani minden munkát, miközben a CPU egy feltétel futásidejű ellenőrzésére vár.
Ezért a CPU belül nem vár. Elágazás-előrejelzőt használ, hogy megtippelje egy feltétel kimenetelét, és spekulatívan elkezdi végrehajtani a megjósolt útvonalat.
Ha a jóslat helyes, a végrehajtás úgy folytatódik, mintha nem is lett volna elágazás. Ha viszont a jóslat hibás, a CPU-nak el kell dobnia a megjósolt útvonalon végzett munkát, és újra kell kezdenie a helyes útvonalon.
Ezt hibás elágazás-előrejelzésnek nevezzük, és költséges: késleltetéssel jár.
Ha egy elágazást nehéz megjósolni, ezek a hibás előrejelzések összeadódnak.
Ilyenkor néha jobb teljesen elkerülni az elágazást, hogy ugyanazok az utasítások fussanak le, a feltételtől függetlenül.
Az olyan kódot, amely jcc nélkül választ az értékek között, elágazás nélkülinek nevezzük.
Többféleképpen is felépíthető a kód úgy, hogy ritkábban ágazzon el, vagy hogy a megmaradó elágazásai kiszámíthatóbbak legyenek. Az x86-64 különösen két utasításcsaládot kínál, amelyeket gyakran használnak elágazás nélküli kód írásához.
A cmovcc utasításcsalád csak akkor másolja a forrásoperandust a célba, ha egy adott feltétel teljesül.
Ellenkező esetben a cél változatlan marad.
A cc utótag ugyanazt a névadást követi, mint a jcc-nél, ugyanazzal a jelentéssel:
| utasítás | mozgatás feltétele |
|---|---|
cmove |
A == B, miután cmp A, B
|
cmovne |
A != B, miután cmp A, B
|
cmovl |
A < B (előjeles), miután cmp A, B
|
cmovb |
A < B (előjel nélküli), miután cmp A, B
|
cmovg |
A > B (előjeles), miután cmp A, B
|
cmova |
A > B (előjel nélküli), miután cmp A, B
|
A szabályok hasonlóak, mint a jcc-nél:
l és a g az előjeles összehasonlításokhoz használatos, a b és az a pedig az előjel nélküliekhez.e-t fűzünk, az az egyenlőséget is magában foglalja.n hozzáfűzése tagadja a feltételt.cmovz és a cmovc.Vegyük egy rdi-ben lévő előjeles egész szám abszolút értékét, amelyet rax-ben adunk vissza.
jcc-vel megírva a függvény két útvonal közül választ:
abs_branch:
mov rax, rdi
cmp rax, 0
jge .done
neg rax
.done:
ret
cmovcc-vel megírva mindkét lehetséges érték feltétel nélkül kiszámításra kerül, és a feltételes mozgatás választ közülük:
abs_branchless:
mov rax, rdi
neg rax ; rax = -rdi
cmp rdi, 0
cmovge rax, rdi ; the value in `rdi` is moved to `rax` if rdi >= 0
; otherwise, it stays the same, i.e., -rdi
; now rax = abs(rdi)
ret
Az elágazás nélküli változat mindig ugyanazokat az utasításokat hajtja végre.
Nincs olyan jcc, amit az előrejelzőnek meg kellene tippelnie.
A cmovcc célja csak regiszter lehet.
A forrás lehet regiszter vagy memóriacím, de azonnali érték nem.
A fenti kódban a neg utasítás az eredménytől függően több jelzőbitet is beállít, köztük az előjelbitet (SF).
Ez azt jelenti, hogy a cmp rdi, 0 teljesen elhagyható:
abs_branchless:
mov rax, rdi
neg rax ; rax = -rdi. `neg` sets SF = 1 if the result is negative
cmovs rax, rdi ; if SF == 1 (i.e., rax < 0), replace rax with rdi
; otherwise rax stays = -rdi, which is non-negative
; now rax = abs(rdi)
ret
Pozitív rdi esetén a neg negatív eredményt ad, és SF == 1, így a cmovs visszaállítja rdi-t.
Nulla vagy negatív rdi esetén a neg nem negatív eredményt ad, és SF == 0, így rax megtartja a negált értéket (ami a helyes abszolút érték).
Bár assemblyben a cmp a fő módja az értékek összehasonlításának, sok utasítás a saját eredménye alapján is beállít jelzőbiteket.
Az utasítás által érintett jelzőbitek teljes listája általában az adott utasítás referenciájában található.
A neg esetében konkrétan ezek: SF, ZF, CF, OF és PF.
A setcc utasításcsalád 1-re állítja a céloperandust, ha egy adott feltétel teljesül, egyébként pedig 0-ra.
A cél mindig 8 bites operandus.
A cc utótag ugyanazt a névadást követi, mint a jcc-nél és a cmovcc-nél.
Például a setz 1-re állítja a célt, ha ZF == 1, egyébként pedig 0-ra.
Mivel a cél 8 bites, gyakori, hogy a setcc-t egy movzx követi, amikor szélesebb értékre van szükség:
cmp rdi, rsi
setg al ; al = 1 if rdi > rsi (signed), 0 otherwise
movzx eax, al ; eax (and rax) = 1 or 0, with the upper bits cleared
Ez egy gyakori idiom arra, hogy egy összehasonlítás eredményét 0 vagy 1 egész számmá alakítsuk.
Firmware-t írsz egy retro kézi konzol pontozási rendszeréhez. A gép CPU-ja szerény, és az eredménytábla kijelzője rögzített ütemben frissül. Hogy a kép folyamatos maradjon, a pontozó rutinoknak kiszámítható számú ciklusban kell lefutniuk, függetlenül attól, mit csinál éppen a játékos.
Négy részfeladatod van.
A részfeladatok kódja legyen elágazás nélküli.
Használj cmovcc, setcc utasításokat és aritmetikát feltételes ugrások helyett.
A kézi konzol LCD-jének pontozási területén hat tizedesjegy fér el.
999999 feletti számot nem tud megjeleníteni.
Amikor egy bónusz a futó összeget e határ fölé vinné, a megjelenített összeg inkább a maximumon marad, ahelyett hogy körbefordulna.
Definiálj egy add_bonus függvényt, amely egy aktuális összeg és egy hozzáadandó bónusz alapján az új, 999999-nél levágott összeget adja vissza.
A paraméterek sorrendben:
total: az aktuális pontszámösszeg (mindig 0 és 999999 között)bonus: a hozzáadandó bónusz (mindig nemnegatív)A visszatérési érték total + bonus, ha ez az összeg kisebb vagy egyenlő, mint 999999, egyébként pedig 999999:
add_bonus(500, 100);
// => 600
add_bonus(999990, 50);
// => 999999
add_bonus(999999, 0);
// => 999999
Mindkét paraméter és a visszatérési érték is 64 bites előjeles egész szám.
Feltételezheted, hogy a total + bonus nem csordítja túl a 64 bites előjeles egészt.
A kézi konzol a mentési fájljában egy kis ranglistát vezet a játékos legjobb friss pontszámairól. Minden játék után az új pontszám bekerül a ranglista megfelelő helyére. A rendező rutin megkérdezi, hogy két pontszám közül melyik van előrébb, és a választ egy kis egész számként várja.
Definiálj egy compare_scores függvényt, amely két pontszám alapján a következőt adja vissza:
+1, ha az első pontszám nagyobb
-1, ha az első pontszám kisebb
0, ha a két pontszám egyenlő
Példa:
compare_scores(500, 300);
// => 1
compare_scores(300, 500);
// => -1
compare_scores(500, 500);
// => 0
Mindkét paraméter és a visszatérési érték is 64 bites előjeles egész szám.
A kézi konzol a linkkábelén keresztül csatlakozhat egy másik egységhez, hogy többjátékos mérkőzés után megosszák a pontszámokat. A kábel elektromosan zajos, és a beérkező bájtok időnként megsérülnek, az érvényes minimum alatti vagy az érvényes maximum feletti értékeket eredményezve. Az érvényesítő minden beérkező pontszámot az érvényes tartományba vág, mielőtt a rendszer többi része látná.
Definiálj egy validate_score függvényt, amely egy nyers pontszám és az érvényes határok alapján a [min, max] tartományba vágott pontszámot adja vissza.
A paraméterek sorrendben:
score: a linkkábelen érkező nyers pontszámmin: a legkisebb megengedett pontszámmax: a legnagyobb megengedett pontszámA visszatérési érték min, ha score < min, max, ha score > max, egyébként pedig score.
Feltételezheted, hogy min <= max.
Példa:
validate_score(450, 0, 500);
// => 450
validate_score(-50, 0, 1530);
// => 0
validate_score(1234567, 0, 2999);
// => 2999
Minden paraméter és a visszatérési érték is 64 bites előjeles egész szám.
Minden játékszesszió végén a kézi konzol átvizsgálja a mentési fájljában lévő legutóbbi játékok naplóját, hogy megtalálja a valaha rögzített két legmagasabb pontszámot. A napló a memóriában egy egyszerű tömb, amely 64 bites előjeles egészeket tartalmaz. Az átvizsgálás egyszer végigmegy a tömbön, és két futó maximumot tart: az eddig látott legmagasabbat és a második legmagasabbat.
Vedd figyelembe, hogy mindkét szám legalább 0 kell hogy legyen.
A negatív számokat figyelmen kívül hagyja.
A függvény első változata egymásra épülő feltételes ugrásokkal íródott. Minden jelölt esetében a kód megkérdezi, hova illik (az első fölé, az első és a második közé, vagy sehova), és ennek megfelelően eltolja a futó maximumokat, elvetve minden negatív eredményt.
Ez működik. Mivel azonban a bemeneti értékek véletlenszerűek, minden feltételes ugrás sok hibás elágazás-előrejelzést eredményez. Ezért döntöttél úgy, hogy a fő ciklust elágazás nélkülire írod át.
Az alábbiakban a függvény látható, amelyben az elágazásokat tartalmazó rész ki van kommentelve:
; rdi = output buffer for two elements, rsi = input array address, rdx = number of elements in array
top_two:
xor r8d, r8d ; first = 0
xor r9d, r9d ; second = 0
xor ecx, ecx ; index = 0
test rdx, rdx
jz .done
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; BRANCHY CODE TO REFACTOR
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;.loop:
; mov rax, qword [rsi + 8*rcx] ; candidate
; inc rcx
; cmp rax, r8
; jle .check_second ; candidate <= first, try second
; mov r9, r8 ; second = first
; mov r8, rax ; first = candidate
; cmp rcx, rdx
; jb .loop ; go to next iteration
; jmp .done ; otherwise, we are done
;.check_second:
; cmp rax, r9
; jle .loop ; candidate <= second, go to next iteration
; mov r9, rax ; second = candidate
; cmp rcx, rdx
; jb .loop ; go to next iteration
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.done:
mov qword [rdi], r8 ; save first
mov qword [rdi + 8], r9 ; save second
ret
Cseréld le a kikommentelt részt egy elágazás nélküli implementációra. Az új kódban az egyetlen megengedett ugrás az, amely visszaugrik a ciklus elejére, hogy megnézze a tömb következő elemét.
A függvénynek nincs visszatérési értéke, és ugyanazokat a paramétereket kapja, mint az elágazásokat tartalmazó változat:
out: kimeneti puffer, ahova a függvény a két legmagasabb nemnegatív pontszámot írja csökkenő sorrendben, 64 bites előjeles egészekkéntarray: 64 bites előjeles egészek bemeneti tömbjelength: a tömb elemeinek száma, 64 bites előjel nélküli egészkéntHa a tömb ismétlődő értékeket tartalmaz, a duplikátumok mindkét kimeneti helyen megjelenhetnek, ha ők a két legmagasabb pontszám.
Iratkozz fel az Exercism-re, hogy megtanuld és elsajátítsd a(z) x86-64 Assembly nyelvet 22 fogalom130 feladat segítségével, valódi emberi mentorálással, mindez ingyen.