Minden eddigi SIMD-művelet sávonként működött.
Az eredmény i sávjában lévő érték a bemenetek i sávjából számítódik ki.
Sok olyan helyzet van azonban, amikor szükséges vagy kívánatos az értékek mozgatása a sávok között. Előfordulhat például, hogy a sávok nincsenek a szükséges számításhoz megfelelő sorrendben.
Számos olyan SIMD-utasítás létezik, amely különböző módon mozgat adatot a sávok között.
Egy keverés átrendezi egy regiszter bájtjait vagy sávjait, és minden cél sávot egy tetszőleges helyen lévő forrás sávból vesz. Ugyanazt a sávot több különböző célhoz is kiválaszthatják, így értékek szórására is képesek.
A keverő utasítások méretüktől és végrehajtási tartományuktól függően eltérően viselkednek.
A pshufd utasítás átrendezi a forrás négy 32 bites sávját a célba.
A kiválasztás egy 8 bites azonnali érték, amelyet négy 2 bites mezőként olvasunk be, mindegyiket egy cél sávhoz. Minden mező kijelöli, hogy a négy forrás sáv közül melyiket másoljuk az adott cél sávba:
| mező | sáv indexe |
|---|---|
00 |
0 |
01 |
1 |
10 |
2 |
11 |
3 |
A mező helye az azonnali értékben, jobbról balra olvasva, megmutatja, hová kerül a kiválasztott sáv. Egy forrás sáv többször is kiválasztható, így egyetlen sáv szórható ki az egész regiszterbe:
; The bit fields are read right-to-left
pshufd xmm1, xmm0, 0b00_01_10_11 ; reverse: lane 0 takes source 0b11 (3), lane 1 takes source 0b10 (2), and so on
pshufd xmm3, xmm2, 0b00_00_00_00 ; broadcast source lane 0 into all four lanes
Két lebegőpontos keverő utasítás létezik, amelyek ugyanazt az általános szintaxist követik: shuf + p + a méret utótagja (vagy s, vagy d).
Ezek is azonnali értékkel választják ki a sávokat.
A shufps ugyanazt a 2 bites mezőkódolást használja, mint a korábbi egész típusú keverések.
Mivel azonban egy 128 bites operandusban csak 2 darab 64 bites sáv van, a shufpd csak 1 bites mezőkódolást használ.
Ezek az utasítások abban különböznek egész típusú megfelelőiktől, hogy a sávokat két operandusból veszik, nem egyből:
shufps xmm0, xmm1, 0b11_10_01_00 ; xmm0 = {xmm0[0], xmm0[1], xmm1[2], xmm1[3]}
shufps xmm2, xmm3, 0b00_00_00_00 ; xmm2 = {xmm2[0], xmm2[0], xmm3[0], xmm3[0]}
shufpd xmm4, xmm5, 0b0_1 ; xmm4 = {xmm4[1], xmm5[0]}
Mivel mindkét operandus táplálja az eredményt, egy lépésben összefűzhetünk velük két vektort. Ha a forrás és a cél ugyanaz a regiszter, minden sáv abból származik.
A pshufb a legáltalánosabb keverés.
Bár a pshufb és a pshufd neve nagyon hasonló, és csak a méret utótagjában tér el, egészen különböző műveleteket végeznek.
Először is, míg a pshufd azonnali értékkel választja ki a sávok pozícióját, a pshufb egy vezérlővektort használ a forrásoperandusban.
Ez a vezérlővektor egy xmm regiszter vagy egy 16 bájtos memóriaoperandus.
A 16 cél sáv mindegyikénél a vezérlővektor alsó négy bitje adja egy forrásbájt indexét, 0-tól 15-ig.
Ha a vezérlővektor i sávja a j forrásbájt-indexet tartalmazza, akkor a cél j-th sávja az i-th sávba kerül.
Ez rávilágít a két utasítás közötti második különbségre.
Míg a pshufd egy másik forrásoperandusból veszi a keverendő sávokat, a pshufb a célban helyben végzi el a keverést.
section .rodata
align 16
reverse: db 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
section .text
fn:
pshufb xmm0, [rel reverse]
; xmm0[0] = xmm0[reverse[0]] (xmm0[15])
; xmm0[1] = xmm0[reverse[1]] (xmm0[14])
; ...
; in the end, the bytes in xmm0 are reversed
A pshufb ennél is rugalmasabb: bármelyik sávot törölheti.
Ha a vezérlővektor i sávjában a legfelső bit be van állítva, akkor a cél i sávja nullázódik.
Így a pshufb egyetlen utasításban egyszerre végez tetszőleges bájtpermutációt és szelektív nullázást.
Mivel a pshufb bájt szinten működik, különböző méretű sávok keverésére is használható, ha a szomszédos sávokat csoportosítjuk.
Például dwordok keverésére is használható:
section .rodata
align 16
input: dd 13, 25, 37, 49 ; each number takes 4 bytes
reverse_dwords: db 12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3 ; aligned too, since input spans 16 bytes
section .text
fn:
movdqa xmm0, [rel input] ; xmm0 = {13, 25, 37, 49}
pshufb xmm0, [rel reverse_dwords]
; xmm0 = {49, 37, 25, 13}
Az unpack utasítások két operandusból vesznek sávokat, és összefonják őket, felváltva a kettő között. Léteznek egész és lebegőpontos változataik, és nagyrészt ugyanazt az általános szintaxist követik, két eltéréssel:
l vagy h utótag, amely jelzi, hogy az operandusok alsó (l) vagy felső (h) felén működik-e.bw vagy qdq (a 16 bájt méretutótagja dq, mint a movdqu esetében).punpcklwd xmm0, xmm1 ; p + unpck + l + wd
; this interleaves the first 4 words of xmm0 and xmm1 into 4 dwords
; for each dword in the result, the first word is taken from xmm0 and the second, from xmm1
; xmm0 = {xmm0[0], xmm1[0], xmm0[1], xmm1[1], xmm0[2], xmm1[2], xmm0[3], xmm1[3]}
unpckhps xmm2, xmm3 ; unpck + h + p + s
; this interleaves the high 32-bit floats of xmm2 and xmm3
; xmm2 = {xmm2[2], xmm3[2], xmm2[3], xmm3[3]}
A pack utasítások az ellenkező irányba haladnak: két operandus sávjait a cél félszélességű sávjaiba kombinálják.
Ezek az utasítások nem kapnak p előtagot.
Azon kívül a szintaxis olyan elemeket kombinál, amelyeket már láttunk:
pack.s vagy u annak jelzésére, hogy a kimeneti értékek előjelesek vagy előjel nélküliek.s a telítésre, ahogy a korábbi fogalomban látott telítéses aritmetikában.unpck-hoz képest).Mivel a pack művelet szűkítő, nincs szüksége l vagy h utótagra:
packssdw xmm0, xmm1 ; pack + s (for signed) + s (for saturating) + dw (dword to word)
packuswb xmm2, xmm3 ; pack + u (for unsigned) + s (for saturating) + wb (word to byte)
Ezek az utasítások telítő műveletek, azaz a kimeneti értékek a szűkebb tartományba lesznek szorítva. Dwordból wordbe és wordből bájtba működnek. Nincs qwordból dwordbe változat.
Vegyük észre, hogy a bemenet mindig előjelesként értelmeződik.
A típus, akár előjeles, akár előjel nélküli, a kimenetre vonatkozik.
Ez jelöli a leszorítás tartományát.
Például a packsswb egy előjeles bájt tartományára szorítja az értéket, azaz [-128, 127]-re.
Az eredmény alsó sávjai a céloperandusból, a felső sávjai a forrásoperandusból származnak:
packusdw xmm0, xmm1 ; 8 words, each clamped to 0..65535
; xmm0 = {xmm0[0], xmm0[1], xmm0[2], xmm0[3], xmm1[0], xmm1[1], xmm1[2], xmm1[3]}
Ezeknek az utasításoknak nincs lebegőpontos megfelelőjük.
Eddig mindig a movq/movd utasításokkal mozgattunk adatot SIMD-regiszter és általános célú regiszter között.
Ezek az utasítások csak a SIMD-regiszter alsó sávjába írhatnak, illetve abból olvashatnak, és íráskor az összes többi sávot törlik.
Vannak olyan utasítások, amelyek ugyanezt bármelyik sávra elvégzik, nem csak az elsőre, és a többi sávot érintetlenül hagyják:
Mindkettő az egész típusú szintaxist követi: p + insr/extr + a méret utótagja (b, w, d vagy q).
Mindkét esetben az általános célú regiszter általában 32 bites.
Csak a pinsrq és a pextrq igényel 64 bites operandust.
A memóriaoperandus mérete mindig a művelet méretével egyezik: 8 bites a pinsrb/pextrb, 16 bites a pinsrw/pextrw esetében, és így tovább.
pinsrb xmm0, eax, 5 ; replace byte 5 of xmm0 with the low byte of eax
pextrb byte [rdx], xmm0, 5 ; copy byte 5 of xmm0 into the memory location indicated by rdx
Ne feledjük, hogy ezek az utasítások, akárcsak a movd és a movq, egyszerűen nyers bájtokat mozgatnak.
Ez azt jelenti, hogy memóriában vagy általános célú regiszterben tárolt lebegőpontos értékekhez is használhatók.
Egy szoftveres képi feldolgozó pipeline belső ciklusait írod meg, azt a szakaszt, amely előkészíti a textúrákat és egyesíti a rétegeket, mielőtt azok a képernyőre kerülnének. A pipeline egyszerre egy blokkot dolgoz fel, ugyanazt a műveletet alkalmazva a teljes blokkra.
Egy pixel négy 1 bájtos csatornából áll: vörös, zöld, kék és alfa, ebben a sorrendben (RGBA).
Egy blokk 4 pixel, azaz összesen 16 bájt.
Öt részfeladat vár rád.
Az operandusokat memóriacímeken keresztül kapod meg, az eredményt pedig egy eredménycímre írod. Ebben a feladatban minden memóriacím 16 bájtos határra igazított.
Az ebben a feladatban végzett számításokat SIMD utasításokkal kell elvégezni, nem skaláris műveletekkel.
A textúrák RGBA formában tárolódnak, de a framebuffer, amelybe ez a pipeline rajzol, BGRA sorrendben várja a pixeleket: a vörös és a kék csatorna felcserélve, a zöld és az alfa csatorna a helyén marad.
A kép blokkok sorozataként érkezik, és minden blokk ugyanúgy alakul át.
Valósítsd meg a to_display_order függvényt, amely egy teljes képet RGBA-ból BGRA-ba alakít át, egyszerre egy blokkot.
A csatornák átrendezését vezérlő maszkot egy csomagolt konstansként érdemes definiálnod a memóriában, és minden blokknál újra felhasználnod.
A függvény argumentumai, ebben a sorrendben:
result: egy puffer memóriacíme, ahová az átalakított blokkok kerülnek, blokkonként 16 bájt.pixels: a forrásblokkok memóriacíme, blokkonként 4 pixel, minden pixel 4 bájt RGBA sorrendben.block_count: a blokkok száma, mindig nagyobb, mint 0.pixels = {200, 64, 32, 255, 10, 20, 30, 40, 0, 0, 0, 255, 12, 34, 56, 78,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} // 2 blocks
block_count = 2
result = {32, 64, 200, 255, 30, 20, 10, 40, 0, 0, 0, 255, 56, 34, 12, 78,
3, 2, 1, 4, 7, 6, 5, 8, 11, 10, 9, 12, 15, 14, 13, 16}
A függvénynek nincs visszatérési értéke.
Egy terület törléséhez vagy egy egyszínű sáv kifestéséhez a pipeline ugyanazt a színt írja a terület minden pixelére.
Valósítsd meg a fill_region függvényt, amely block_count blokkból álló területet tölt ki egyetlen szín másolataival.
A függvény argumentumai, ebben a sorrendben:
result: egy puffer memóriacíme, ahová a kitöltött blokkok kerülnek, blokkonként 16 bájt.color: egy pixel memóriacíme, 4 bájt RGBA sorrendben.block_count: a kitöltendő blokkok száma, mindig nagyobb, mint 0.color = {18, 52, 86, 120}
block_count = 2
result = {18, 52, 86, 120, 18, 52, 86, 120, 18, 52, 86, 120, 18, 52, 86, 120,
18, 52, 86, 120, 18, 52, 86, 120, 18, 52, 86, 120, 18, 52, 86, 120}
A függvénynek nincs visszatérési értéke.
Két egycsatornás réteget kell egyetlen pufferbe összefésülni úgy, hogy a mintáik egymásba fonódjanak. Az eredményben felváltva következik egy minta az első rétegből, majd egy a másodikból.
Valósítsd meg a weave_scanlines függvényt, amely két, egyenként 16 mintából álló sort fésül össze egyetlen 32 mintás sorrá.
A függvény argumentumai, ebben a sorrendben:
result: egy puffer memóriacíme, ahová a 32 összefésült minta kerül.first: az első sor memóriacíme, 16 minta, mindegyik 8 bites érték.second: a második sor memóriacíme, 16 minta, mindegyik 8 bites érték.first = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
second = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115}
result = {0, 100, 1, 101, 2, 102, 3, 103, 4, 104, 5, 105, 6, 106, 7, 107,
8, 108, 9, 109, 10, 110, 11, 111, 12, 112, 13, 113, 14, 114, 15, 115}
A függvénynek nincs visszatérési értéke.
A fényerőt növelő szakasz minden mintát 16 bites munkapontossággal skáláz, így előfordulhat, hogy egy túl fényes minta meghaladja a 255-öt, egy különbség pedig 0 alá csökken.
A végső szakasz ezeket a munkapontosságú értékeket 8 bites mintákká szűkíti a megjelenítéshez: a 0 alatti értékeket 0-ra, a 255 felettieket 255-re vágja.
Valósítsd meg a pack_samples függvényt, amely két, egyenként 8 munkapontosságú értékből álló csoportot szűkít egyetlen 16 mintás sorrá, sorrendben.
A függvény argumentumai, ebben a sorrendben:
result: egy puffer memóriacíme, ahová a 16 levágott minta kerül, mindegyik 8 bites érték.first: az első 8 munkapontosságú érték memóriacíme, mindegyik 16 bites előjeles egész szám.second: a következő 8 munkapontosságú érték memóriacíme, mindegyik 16 bites előjeles egész szám.first = {300, -5, 128, 255, 0, 400, 64, 200}
second = {255, 256, -1, 100, 50, 1000, 7, 0}
result = {255, 0, 128, 255, 0, 255, 64, 200, 255, 255, 0, 100, 50, 255, 7, 0}
A függvénynek nincs visszatérési értéke.
A textúra- és csúcsadatok gyakran egymásba fonva érkeznek, minden pont x és y értéke együtt csomagolva.
A hatékony feldolgozáshoz viszont gyakran szét kell választani őket: az összes x érték az egyik vektorban, az összes y érték a másikban.
Valósítsd meg a split_coordinates függvényt, amely négy egymásba font (x, y) pontot választ szét egy x koordinátákból álló és egy y koordinátákból álló vektorra.
A függvény argumentumai, ebben a sorrendben:
xs: egy puffer memóriacíme, ahová a 4 x koordináta kerül, 4 darab 32 bites lebegőpontos szám.ys: egy puffer memóriacíme, ahová a 4 y koordináta kerül, 4 darab 32 bites lebegőpontos szám.first: az első két pont memóriacíme, 4 darab 32 bites lebegőpontos szám, {x0, y0, x1, y1} formában.second: a következő két pont memóriacíme, 4 darab 32 bites lebegőpontos szám, {x2, y2, x3, y3} formában.first = {0.0, 0.5, 1.0, 1.5} // {x0, y0, x1, y1}
second = {2.0, 2.5, 3.0, 3.5} // {x2, y2, x3, y3}
xs = {0.0, 1.0, 2.0, 3.0} // {x0, x1, x2, x3}
ys = {0.5, 1.5, 2.5, 3.5} // {y0, y1, y2, y3}
A függvénynek nincs visszatérési értéke.
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.