A skaláris kód arra támaszkodik, hogy a különféle utasítások jelzőbiteket állítanak be, és ezek alapján egy adott feltétel hatására elágazik. A csomagolt értékek viszont nem egyetlen értéket, hanem egyszerre többet képviselnek. Egyetlen feltétel az egyik sávban megbukhat, miközben a másikban teljesül.
Ezért a SIMD-kód alapértelmezés szerint elágazásmentes.
A jelzőbitekre támaszkodás helyett a csomagolt összehasonlítások általában egy maszkot állítanak elő a céloperandusba.
Az összehasonlítás minden sáv esetében az egész sávot egyesekkel tölti fel, ha a feltétel igaz, és nullákkal, ha hamis.
Előjeles egészként értelmezve egy igaz sáv -1, egy hamis sáv pedig 0.
Ez a maszk aztán bitenkénti műveletekkel kombinálható, hogy adott sávokat kiszűrjünk.
A skaláris cmp abban az értelemben általános, hogy egyszerre több jelzőbit beállítására szolgál.
Egy másik utasítás aztán felhasználhatja ezeket a jelzőbiteket, hogy elágazást hajtson végre vagy számításokat végezzen.
Mivel azonban egy csomagolt összehasonlítás egyszerre ellenőriz egy feltételt és számít ki egy maszkot, nem általános. Az összehasonlításnak meg kell adni a pontosan vizsgált feltételt.
Erre kétféle módon van lehetőség:
eq az egyenlőséghez, gt a nagyobb, mint relációhoz.
A többi változat ezek eredményének kombinálásával áll elő.A szintaxis ugyanazt a szerkezetet követi, amit már láttunk, azzal a kivétellel, hogy az egész összehasonlításoknál egy meghatározott feltétel-utótagot használunk:
p + cmp + feltétel + méret (b, w, d vagy q).cmp + p + méret (s vagy d).
A feltételt egy immediate konstansban, extra operandusként adjuk át.Ahogy említettük, egészeknél csak egyenlőségre és nagyobb, mint relációra van összehasonlítás:
| utasítás | leírás |
|---|---|
pcmpeqb, pcmpeqw, pcmpeqd, pcmpeqq
|
sávonkénti egyenlőség |
pcmpgtb, pcmpgtw, pcmpgtd, pcmpgtq
|
sávonkénti előjeles nagyobb, mint |
movdqa xmm0, [rel scores]
pcmpgtd xmm0, [rel threshold] ; lane i = 0xFFFFFFFF (-1) if scores[i] > threshold[i], else 0
Kisebb, mint összehasonlítás létrehozásához használd a gt-t felcserélt operandusokkal: a < b == b > a.
Figyeld meg, hogy az összehasonlítás előjeles. Előjel nélküli összehasonlításhoz mindkét operandus legfelső bitjét át kell billenteni. Ez egy olyan maszkkal végzett XOR művelettel tehető meg, amelyben csak a legfelső bit van beállítva.
Két hasznos idióma:
Például:
pxor xmm4, xmm4 ; xmm4 = all zeros
pcmpeqd xmm7, xmm7 ; xmm7 = all ones
A csupa nulla és a csupa egyes gyakori maszkok arra, hogy rendre a „mindenhol hamis” és a „mindenhol igaz” állapotot kódolják.
Arra is használhatók, hogy csomagolt 0-t vagy csomagolt -1-et ábrázoljanak, amelyek gyakori őrértékek.
Például a string végét jelölő NUL egy 0.
A lebegőpontos sávok más felépítést használnak: egyetlen utasítás, a cmpps (illetve 64 bites sávoknál a cmppd), amelynél a feltétel immediate konstansként szerepel:
movaps xmm0, [rel readings]
cmpps xmm0, [rel limits], 1 ; condition 1 is "less than": lane i = all ones if readings[i] < limits[i]
A NASM-nak vannak olyan pszeudo-műveletei is, amelyek a helyes immediate értékre képződnek le, és könnyebben megjegyezhetők.
Az alábbiakban a px-ben szereplő x lehet s (32 bites lebegőpontos) vagy d (64 bites lebegőpontos):
| pszeudo-művelet | immediate | összehasonlítás |
|---|---|---|
cmpeqpx |
0 | a == b |
cmpltpx |
1 | a < b |
cmplepx |
2 | a <= b |
cmpunordpx |
3 | a vagy b NaN |
cmpneqpx |
4 | a != b |
cmpnltpx |
5 | a >= b |
cmpnlepx |
6 | a > b |
cmpordpx |
7 | sem a, sem b nem NaN |
A maszk egy feltétel eredményét kódolja. Ezután arra használható, hogy sávonként válasszunk két értékhalmaz között e feltétel alapján. Az egyik értékből vesszük a sávot ott, ahol a maszk igaz, a másikból ott, ahol hamis:
; result = (a AND mask) OR (b AND NOT mask)
movdqa xmm2, xmm0 ; xmm0 holds the mask, keep a copy
pand xmm2, xmm3 ; xmm2 = a AND mask: lanes of a where mask is true
pandn xmm0, xmm4 ; xmm0 = NOT mask AND b: lanes of b where mask is false
por xmm2, xmm0 ; combine the two halves
Figyeld meg, hogy a pandn aszimmetriája itt kifizetődik: a maszk a céloperandusban van, ott negálódik, és egyetlen utasításban választ a b-ből.
Ez a minta az elágazásmentes kiválasztás csomagolt formája.
Minden sáv kiszámítódik, és egyedül a maszk dönti el, melyik érték marad meg; jcc sehol sem szerepel.
Vannak olyan utasítások, amelyek közvetlenül elvégzik ugyanezt a kiválasztást, és elemenként egy bitet olvasnak ki egy maszkregiszterből. Ezeket blend utasításoknak nevezzük:
| utasítás | elem | maszk forrása |
|---|---|---|
pblendvb |
bájt | implicit xmm0
|
blendvps |
32 bites sáv | implicit xmm0
|
blendvpd |
64 bites sáv | implicit xmm0
|
Figyeld meg, hogy az első utasítás az egész szintaxist követi, míg a másik kettő a lebegőpontosét. Mivel azonban ezek az utasítások egyszerűen nyers bájtokat választanak ki, bármelyikük használható egészekkel és lebegőpontos számokkal is.
A blend minden elemnél megtartja a céloperandust, ha a hozzá tartozó maszkelem legfelső bitje törölt, és a forrásoperandust veszi, ha az be van állítva.
Csak azt a legfelső bitet veszi figyelembe, amit egy összehasonlításból származó maszk teljesít is, hiszen a sávjai csupa egyesek vagy csupa nullák.
A maszkregiszter mindig xmm0, ami implicit:
movaps xmm0, [rel mask] ; the selecting mask must be in xmm0
movaps xmm1, [rel b] ; destination: kept where the mask bit is clear
blendvps xmm1, [rel a] ; source: taken where the mask bit is set
A pblendvb-vel más méreteknél is kiválaszthatók sávok egy összehasonlítási maszkból.
Mivel egy igaz sávban minden bájt csupa egyes, a pblendvb az összeset kiválasztja.
Ezek az utasítások mind a v betűt fűzik az elvégzett művelet (blend) után.
Ez a v a változó szót jelöli, mert a kiválasztás nem statikus: egy regisztertől függ.
Vannak v nélküli változatok is, amelyek egy immediate érték alapján választanak.
Ezek ugyanazt a mintát követik: kiválasztják az i sávot, ha az immediate i. bitje be van állítva.
Bár hatékony, a SIMD-kód messze nem olyan rugalmas, mint a skaláris kód. Sok helyzetben szükség van arra, hogy egy csomagolt regiszterből visszatérjünk a skaláris utasítások világába.
A movmsk utasításcsalád hidat képez a két világ között.
Ezek az utasítások minden sáv legfelső bitjét emelik ki egy általános célú regiszterbe:
| utasítás | mit gyűjt ki | az eredmény szélessége |
|---|---|---|
pmovmskb |
16 bájt legfelső bitje | 16 bit |
movmskps |
4 duplaszó legfelső bitje | 4 bit |
movmskpd |
2 négyszeres szó legfelső bitje | 2 bit |
Ha összehasonlítás után használjuk, minden beállított bit egy „igaz” sávot jelöl, minden törölt bit pedig egy „hamis” sávot.
Ez az eredmény aztán a szokásos módon manipulálható skaláris utasításokkal.
Például a popcnt megszámolja az egyezések számát, a tzcnt pedig megkeresi az elsőt.
Az általános célú regiszter 32 vagy 64 bites szélességű lehet.
A skaláris test utasításnak is van csomagolt változata: a ptest.
Hasonlít a skaláris megfelelőjéhez abban, hogy AND műveletet végez két operandus között, anélkül hogy módosítaná őket.
A test-tel ellentétben a ptest ANDN műveletet is végez, amely tagadja az első operandust.
Így a ptest felfogható a pand és a pandn nem roncsoló változataként, amely az eredmény alapján állítja be a jelzőbiteket.
Akárcsak ez a két utasítás, a ptest is egyetlen sávként kezeli a teljes SIMD-regisztert, ezért nem vesz fel méretelőtagot.
Ha egy AND művelet eredménye 0, akkor a ZF beáll, ha pedig az ANDN művelet eredménye 0, akkor a CF áll be.
Ez azt jelenti, hogy a ptest egyszerre használható csupa egyes és csupa nulla maszk vizsgálatára:
ptest-et egy regiszterre önmagával futtatjuk, az csak akkor állítja be a ZF-et, ha a regiszter csupa nulla.
Ez utánozza azt a gyakori skaláris idiómát, amikor a test-et egy regiszterre önmagával futtatjuk 0 ellenőrzésére.ptest-et egy regiszterre csupa egyes maszkkal futtatjuk, az csak akkor állítja be a CF-et, ha a regiszter csupa egyes.
Ráadásul a ZF-et csak akkor állítja be, ha a regiszter csupa nulla, így egyszerre mindkét maszk ellenőrizhető.pxor xmm0, xmm0 ; all zeros
pcmpeqb xmm1, xmm1 ; all ones
pcmpeqb xmm2, xmm2
ptest xmm0, xmm0 ; ZF set: a register against itself detects all zeros
ptest xmm0, xmm1 ; ZF set, CF clear: xmm0 is all zeros, not all ones
ptest xmm2, xmm1 ; CF is set only if xmm2 is all ones
A ptest eredménye a szokásos módon használható elágazáshoz vagy elágazásmentes utasításokban, például a setcc-ben vagy a cmovcc-ben.
Egy iskola pontozóállomását vezeted, és blokkonként pontozod az osztály eredményeit.
Minden blokk 4 eredményt tartalmaz, és az állomás ugyanazt a műveletet alkalmazza a blokk minden eredményére. A pontszám egy 32 bites lebegőpontos szám. Több lépés is maszkkal dolgozik: ez egy 4 sávból álló blokk, ahol minden sáv vagy csupa egyes (egy igen az adott eredményre), vagy csupa nulla (egy nem).
Öt részfeladat vár rád. Az operandusokat memóriacímeken keresztül kapod. Egyes részfeladatok az eredménycímre írják a válaszukat, mások viszont közvetlenül adják vissza.
Ebben a feladatban minden memóriacím 16 bájtra van igazítva.
A feladat számításait SIMD utasításokkal kell elvégezni.
Az első lépés minden eredményt egy küszöbhöz viszonyítva pontoz. Egy eredmény akkor kerül a küszöb fölé, ha a pontszáma szigorúan nagyobb, mint a küszöb. A küszöbnél kisebb vagy azzal egyenlő pontszám viszont nem.
Valósítsd meg a flag_above_threshold függvényt, amely olyan maszkot épít, ahol minden küszöb fölötti pontszámhoz csupa egyes sáv, egyébként pedig csupa nulla sáv tartozik.
A függvény a következő argumentumokat kapja, ebben a sorrendben:
result: annak a puffernek a memóriacíme, ahová a maszk 4 sávját írja.scores: a pontszámok memóriacíme, benne 4 darab 32 bites normál lebegőpontos számmal (soha nem NaN).thresholds: az egyes sávok küszöbének memóriacíme, benne 4 darab 32 bites normál lebegőpontos számmal (soha nem NaN).scores = {72.0, 55.0, 90.0, 40.0}
thresholds = {60.0, 60.0, 60.0, 60.0}
result = {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}
A függvénynek nincs visszatérési értéke.
Egy külön jelentés emeli ki a tökéletes eredményeket, azokat, amelyek elérték a lehetséges legmagasabb pontszámot.
Valósítsd meg a flag_perfect függvényt, amely olyan maszkot épít, ahol minden, a maximumával egyenlő pontszámhoz csupa egyes sáv, egyébként pedig csupa nulla sáv tartozik.
A függvény a következő argumentumokat kapja, ebben a sorrendben:
result: annak a puffernek a memóriacíme, ahová a maszk 4 sávját írja.scores: a pontszámok memóriacíme, benne 4 darab 32 bites normál lebegőpontos számmal (soha nem NaN).maxima: az egyes sávok maximális pontszámának memóriacíme, benne 4 darab 32 bites normál lebegőpontos számmal (soha nem NaN).scores = {100.0, 88.0, 100.0, 73.0}
maxima = {100.0, 100.0, 100.0, 100.0}
result = {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}
A függvénynek nincs visszatérési értéke.
Minden pontszám 1 és 3 közötti rangot ér el:
50.0-s sikerküszöbön lévő vagy annál alacsonyabb pontszámért.Valósítsd meg az assign_ranks függvényt, amely kiírja az egyes pontszámok rangját.
A sikerküszöböt és a rangértékeket érdemes csomagolt konstansként definiálni a memóriában. Az előző két részfeladat függvényei újra felhasználhatók: egy pontszám legalább 2-es rangú, ha a küszöb fölött van, és 3-as rangú, ha megegyezik a maximummal.
A függvény a következő argumentumokat kapja, ebben a sorrendben:
result: annak a puffernek a memóriacíme, ahová a 4 rangot írja, mindegyiket 32 bites előjel nélküli egészként.scores: a pontszámok memóriacíme, benne 4 darab 32 bites normál lebegőpontos számmal (soha nem NaN).maxima: az egyes sávok maximális pontszámának memóriacíme, benne 4 darab 32 bites normál lebegőpontos számmal (soha nem NaN).scores = {40.0, 75.0, 100.0, 60.0}
maxima = {100.0, 100.0, 100.0, 100.0}
result = {1, 2, 3, 2}
A függvénynek nincs visszatérési értéke.
Az év során minden tanuló összesített rangot gyűjt. Az állomás összeszámolja, hogy a teljes csoportban hány rang marad egy sikerküszöb alatt, hogy megtervezze, hány pótórát kell indítani.
Valósítsd meg a count_failures függvényt, amely visszaadja, hogy minden blokkot figyelembe véve hány rang van szigorúan a sikerküszöb alatt.
A küszöböt 4 azonos sávból álló blokként kapod, így egyszer betöltheted, és minden blokkhoz újra felhasználhatod.
A függvény a következő argumentumokat kapja, ebben a sorrendben:
ranks: a rangok memóriacíme, egész számú 4 sávos blokkot alkotnak, és minden rang 32 bites előjel nélküli egész szám.block_count: a 4 sávos blokkok száma, mindig nagyobb, mint 0.pass_threshold: a sikerküszöb memóriacíme, benne 4 azonos 32 bites egész számmal.ranks = {1, 2, 3, 1, 2, 2, 1, 3} // 2 blocks
block_count = 2
pass_threshold = {2, 2, 2, 2}
// => 3
A függvény a darabszámot előjeles 32 bites egészként adja vissza.
Mielőtt az adatokat irattárba helyeznék, az állomás ellenőrzi, hogy tiszta-e a csoport: akkor felel meg, ha egyetlen eredmény sem bukott meg egyetlen blokkban sem.
Valósítsd meg az all_passed függvényt, amely 1-et ad vissza, ha minden tanuló átment, egyébként pedig 0-t.
Egy tanuló akkor megy át, ha a failing tömbben a hozzá tartozó sáv csupa nulla.
A függvény a következő argumentumokat kapja, ebben a sorrendben:
failing: a bukást jelző maszkok memóriacíme, egész számú 4 sávos blokk, ahol minden sáv csupa egyes vagy csupa nulla.block_count: a 4 sávos blokkok száma, mindig nagyobb, mint 0.failing = {0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000} // 2 blocks
block_count = 2
// => 1
A függvény a választ előjeles 32 bites egészként adja vissza: 1 vagy 0.
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.