Obsah
1. Pohled pod kapotu formátu WebAssembly: SIMD (vektorové) operace, 2 část
2. Krátké připomenutí: instrukce určené pro součet vektorů
3. Aritmetické operace součtu, rozdílu a změny znaménka prováděné s celými vektory
4. Způsob překladu funkcí z demonstračního příkladu do instrukcí WebAssembly
5. Tabulka se všemi instrukcemi pro součet, rozdíl a změnu znaménka celých vektorů
6. Aritmetická operace součinu prvků vektorů
7. Překlad operace součinu prvků vektorů s využitím podporovaných instrukcí
8. Tabulka s instrukcemi pro součin odpovídajících si prvků vektorů
9. Způsob překladu součinu prvků vektorů typu byte
10. Aritmetická operace podílu prvků vektorů
11. Překlad operace podílu prvků vektorů s využitím podporovaných instrukcí
12. Tabulka s instrukcemi pro podíl odpovídajících si prvků vektorů
13. Instrukce pro konstrukci nového vektoru
14. Instrukce pro extrakci prvku i pro zpětný zápis prvku do vektoru
15. Překlad složitějších vektorových operací: výpočet skalárního součinu dvou vektorů
16. Výpočet druhé odmocniny prvků vektorů typu float a double
17. Instrukce sloužící pro porovnání odpovídajících si prvků vektorů
18. Tabulka se všemi doposud popsanými instrukcemi
19. Články o SIMD, které doposud na Rootu vyšly
1. Pohled pod kapotu formátu WebAssembly: SIMD (vektorové) operace, druhá část
Na předchozí článek dnes navážeme. Popíšeme si totiž další SIMD (vektorové) instrukce, které jsou oficiálně popsány ve WebAssembly a jsou i podporovány většinou implementací tohoto virtuálního stroje (důležité je zejména to, že jsou podporovány všemi prohlížeči). Ve WebAssembly je přitom definováno přibližně 200 těchto instrukcí – zajímavé však je, že některé instrukce chybí (resp. chybí jejich varianty pro určité typy vektorů), takže instrukční soubor není z tohoto pohledu ortogonální.
Překlad všech dále uvedených demonstračních příkladů budeme provádět následující sekvenci příkazů. První dva příkazy slouží pro překlad do Web Assembly, poslední příkaz pak zpětným překladem ukáže, jaké instrukce jsou uloženy ve výsledném binárním souboru:
clang -Os -msimd128 --target=wasm32 -emit-llvm -c -S ${1}.c
llc -march=wasm32 -filetype=obj ${1}.ll -o ${1}.wasm
wasm-objdump -d ${1}.wasm
Programátoři, co nesnášíte BS, ale máte rádi business! Y Soft je česká firma s globálním dopadem (100+ zemí, 1M+ uživatelů a >100% meziroční růst). R&D úplně bez manažerů (130 developerů). Otevíráme 30 pozic pro Cloud a AI: Praha/Brno/Ostrava/remote. Zodpovědnost ano, mikro-management ne. Pojď někam, kde můžeš věci změnit.
2. Krátké připomenutí: instrukce určené pro součet vektorů
V předchozím článku jsme si kromě dalších informací popsali i instrukce, které zajišťují součet vektorů, tj. takové instrukce, jež provádí součet prvek po prvku. Těchto instrukcí je ve WebAssembly definováno celkem šest, což odpovídá i všem v současnosti podporovaným typům vektorů. Všechny instrukce určené pro součet vektorů jsou vypsány v následující tabulce. Z pohledu pojmenování instrukcí se jedná o „běžný“ součet (add), ovšem prováděný s hodnotami typu, které jsou uvedeny v prefixu názvu instrukce (což je pro WebAssembly typické):
| Operační kód | Jméno instrukce | Struktura vektorů, které se sčítají |
|---|---|---|
| fd 6e | i8×16.add | 16 prvků typu byte (16×8 bitů) |
| fd 8e 01 | i16×8.add | 8 prvků typu word (8×16 bitů) |
| fd ae 01 | i32×4.add | 4 prvky typu double word (4×32 bitů) |
| fd ce 01 | i64×2.add | 2 prvky typu quad word (2×64 bitů) |
| fd e4 01 | f32×4.add | 4 prvky typu single (4×32 bitů) |
| fd f0 01 | f64×2.add | 2 prvky typu double (2×64 bitů) |
3. Aritmetické operace součtu, rozdílu a změny znaménka prováděné s celými vektory
Kromě součtu vektorů nabízí WebAssembly i možnost provádění dalších operací s odpovídajícími si prvky dvou vektorů nebo s prvky jediného vektoru. Nabídka těchto operací se liší podle toho, zda vektory obsahují celočíselné hodnoty nebo hodnoty s plovoucí řádovou čárkou, ovšem nezávisle na typu prvků jsou tři operace společné a plně podporované: součet vektorů (ten již známe), rozdíl vektorů a otočení znaménka u všech prvků jednoho vektoru.
V dnešním prvním demonstračním příkladu si vyzkoušíme, jakým způsobem jsou tyto operace přeloženy do instrukcí WebAssembly. V příkladu nejdříve nadefinovány všechny podporované typy vektorů, tj. vektory se šestnácti prvky typu bajt (byte) až po vektory se dvěma prvky typu double (ovšem šířka vektorů je stále 128 bitů):
typedef signed char i8x16 __attribute__((vector_size(16))); typedef unsigned char u8x16 __attribute__((vector_size(16))); typedef signed short int i16x8 __attribute__((vector_size(16))); typedef unsigned short int u16x8 __attribute__((vector_size(16))); typedef signed int i32x4 __attribute__((vector_size(16))); typedef unsigned int u32x4 __attribute__((vector_size(16))); typedef signed long long int i64x2 __attribute__((vector_size(16))); typedef unsigned long long int u64x2 __attribute__((vector_size(16))); typedef float f32x4 __attribute__((vector_size(16))); typedef double f64x2 __attribute__((vector_size(16)));
Následně, abychom nemuseli stále opisovat prakticky stejný kód, jsou jednotlivé operace nadefinovány formou maker:
#define NEG(type) type neg_##type(type x) {return -x;}
#define ADD(type) type add_##type(type x, type y) {return x+y;}
#define SUB(type) type sub_##type(type x, type y) {return x-y;}
Každé makro je expandováno na funkci, která ve svém jménu obsahuje příslušný typ. Pokud například zavoláme první makro s typem i8×16, provede se expanze na:
i8x16 neg_i8x16(i8x16 x)
{
return -x;
}
Dále si vytvoříme další pomocné makro, které se expanduje na sekvenci tří předchozích maker. Zavoláním tohoto makra dosáhneme expanzi do třiceti funkcí:
#define ALL(type) \
NEG(type) \
ADD(type) \
SUB(type)
ALL(i8x16)
ALL(u8x16)
ALL(i16x8)
ALL(u16x8)
ALL(i32x4)
ALL(u32x4)
ALL(i64x2)
ALL(u64x2)
ALL(f32x4)
ALL(f64x2)
Celý zdrojový kód dnešního prvního demonstračního příkladu vypadá následovně:
typedef signed char i8x16 __attribute__((vector_size(16)));
typedef unsigned char u8x16 __attribute__((vector_size(16)));
typedef signed short int i16x8 __attribute__((vector_size(16)));
typedef unsigned short int u16x8 __attribute__((vector_size(16)));
typedef signed int i32x4 __attribute__((vector_size(16)));
typedef unsigned int u32x4 __attribute__((vector_size(16)));
typedef signed long long int i64x2 __attribute__((vector_size(16)));
typedef unsigned long long int u64x2 __attribute__((vector_size(16)));
typedef float f32x4 __attribute__((vector_size(16)));
typedef double f64x2 __attribute__((vector_size(16)));
#define NEG(type) type neg_##type(type x) {return -x;}
#define ADD(type) type add_##type(type x, type y) {return x+y;}
#define SUB(type) type sub_##type(type x, type y) {return x-y;}
#define ALL(type) \
NEG(type) \
ADD(type) \
SUB(type)
ALL(i8x16)
ALL(u8x16)
ALL(i16x8)
ALL(u16x8)
ALL(i32x4)
ALL(u32x4)
ALL(i64x2)
ALL(u64x2)
ALL(f32x4)
ALL(f64x2)
4. Způsob překladu funkcí z demonstračního příkladu do instrukcí WebAssembly
Po překladu demonstračního příkladu z předchozí kapitoly z céčka do WebAssembly můžeme použít již dříve popsaný nástroj wasm-objdump pro zpětný překlad z bajtkódu do čitelné sekvence (strojových) instrukcí. Získat bychom měli zpětný překlad celkem třiceti funkcí, protože jsme (přes makra) vygenerovali tři céčkovské funkce pro každý z deseti datových typů.
Všechny přeložené funkce mají podobnou strukturu. Pro funkce s jediným parametrem vypadá tato struktura následovně:
func[0] <jméno_operace_jméno_datového_typu>: local.get 0 ... příslušná vektorová operace ... end
Naopak pro funkce se dvěma parametry se struktura nepatrně změní do podoby:
func[0] <jméno_operace_jméno_datového_typu>: local.get 0 local.get 1 ... příslušná vektorová operace ... end
Podívejme se nyní na výsledek překladu:
simd_B.wasm: file format wasm 0x1 Code Disassembly: 000065 func[0] <neg_i8x16>: 000066: 20 00 | local.get 0 000068: fd 61 | i8x16.neg 00006a: 0b | end 00006c func[1] <add_i8x16>: 00006d: 20 01 | local.get 1 00006f: 20 00 | local.get 0 000071: fd 6e | i8x16.add 000073: 0b | end 000075 func[2] <sub_i8x16>: 000076: 20 00 | local.get 0 000078: 20 01 | local.get 1 00007a: fd 71 | i8x16.sub 00007c: 0b | end 00007e func[3] <neg_u8x16>: 00007f: 20 00 | local.get 0 000081: fd 61 | i8x16.neg 000083: 0b | end 000085 func[4] <add_u8x16>: 000086: 20 01 | local.get 1 000088: 20 00 | local.get 0 00008a: fd 6e | i8x16.add 00008c: 0b | end 00008e func[5] <sub_u8x16>: 00008f: 20 00 | local.get 0 000091: 20 01 | local.get 1 000093: fd 71 | i8x16.sub 000095: 0b | end 000097 func[6] <neg_i16x8>: 000098: 20 00 | local.get 0 00009a: fd 81 01 | i16x8.neg 00009d: 0b | end 00009f func[7] <add_i16x8>: 0000a0: 20 01 | local.get 1 0000a2: 20 00 | local.get 0 0000a4: fd 8e 01 | i16x8.add 0000a7: 0b | end 0000a9 func[8] <sub_i16x8>: 0000aa: 20 00 | local.get 0 0000ac: 20 01 | local.get 1 0000ae: fd 91 01 | i16x8.sub 0000b1: 0b | end 0000b3 func[9] <neg_u16x8>: 0000b4: 20 00 | local.get 0 0000b6: fd 81 01 | i16x8.neg 0000b9: 0b | end 0000bb func[10] <add_u16x8>: 0000bc: 20 01 | local.get 1 0000be: 20 00 | local.get 0 0000c0: fd 8e 01 | i16x8.add 0000c3: 0b | end 0000c5 func[11] <sub_u16x8>: 0000c6: 20 00 | local.get 0 0000c8: 20 01 | local.get 1 0000ca: fd 91 01 | i16x8.sub 0000cd: 0b | end 0000cf func[12] <neg_i32x4>: 0000d0: 20 00 | local.get 0 0000d2: fd a1 01 | i32x4.neg 0000d5: 0b | end 0000d7 func[13] <add_i32x4>: 0000d8: 20 01 | local.get 1 0000da: 20 00 | local.get 0 0000dc: fd ae 01 | i32x4.add 0000df: 0b | end 0000e1 func[14] <sub_i32x4>: 0000e2: 20 00 | local.get 0 0000e4: 20 01 | local.get 1 0000e6: fd b1 01 | i32x4.sub 0000e9: 0b | end 0000eb func[15] <neg_u32x4>: 0000ec: 20 00 | local.get 0 0000ee: fd a1 01 | i32x4.neg 0000f1: 0b | end 0000f3 func[16] <add_u32x4>: 0000f4: 20 01 | local.get 1 0000f6: 20 00 | local.get 0 0000f8: fd ae 01 | i32x4.add 0000fb: 0b | end 0000fd func[17] <sub_u32x4>: 0000fe: 20 00 | local.get 0 000100: 20 01 | local.get 1 000102: fd b1 01 | i32x4.sub 000105: 0b | end 000107 func[18] <neg_i64x2>: 000108: 20 00 | local.get 0 00010a: fd c1 01 | i64x2.neg 00010d: 0b | end 00010f func[19] <add_i64x2>: 000110: 20 01 | local.get 1 000112: 20 00 | local.get 0 000114: fd ce 01 | i64x2.add 000117: 0b | end 000119 func[20] <sub_i64x2>: 00011a: 20 00 | local.get 0 00011c: 20 01 | local.get 1 00011e: fd d1 01 | i64x2.sub 000121: 0b | end 000123 func[21] <neg_u64x2>: 000124: 20 00 | local.get 0 000126: fd c1 01 | i64x2.neg 000129: 0b | end 00012b func[22] <add_u64x2>: 00012c: 20 01 | local.get 1 00012e: 20 00 | local.get 0 000130: fd ce 01 | i64x2.add 000133: 0b | end 000135 func[23] <sub_u64x2>: 000136: 20 00 | local.get 0 000138: 20 01 | local.get 1 00013a: fd d1 01 | i64x2.sub 00013d: 0b | end 00013f func[24] <neg_f32x4>: 000140: 20 00 | local.get 0 000142: fd e1 01 | f32x4.neg 000145: 0b | end 000147 func[25] <add_f32x4>: 000148: 20 00 | local.get 0 00014a: 20 01 | local.get 1 00014c: fd e4 01 | f32x4.add 00014f: 0b | end 000151 func[26] <sub_f32x4>: 000152: 20 00 | local.get 0 000154: 20 01 | local.get 1 000156: fd e5 01 | f32x4.sub 000159: 0b | end 00015b func[27] <neg_f64x2>: 00015c: 20 00 | local.get 0 00015e: fd ed 01 | f64x2.neg 000161: 0b | end 000163 func[28] <add_f64x2>: 000164: 20 00 | local.get 0 000166: 20 01 | local.get 1 000168: fd f0 01 | f64x2.add 00016b: 0b | end 00016d func[29] <sub_f64x2>: 00016e: 20 00 | local.get 0 000170: 20 01 | local.get 1 000172: fd f1 01 | f64x2.sub 000175: 0b | end
5. Tabulka se všemi instrukcemi pro součet, rozdíl a změnu znaménka celých vektorů
Všechny doposud popsané vektorové instrukce jsou shrnuty v následující tabulce. Opět je z ní patrné použití prefixu 0×FD a taktéž to, že operace s vektory s prvky typu bajt mají kratší operační kód:
| Operační kód | Jméno instrukce | Operace | Struktura vektorů |
|---|---|---|---|
| fd 61 | i8×16.neg | otočení znaménka | 16 prvků typu byte (16×8 bitů) |
| fd 6e | i8×16.add | součet vektorů | 16 prvků typu byte (16×8 bitů) |
| fd 71 | i8×16.sub | rozdíl vektorů | 16 prvků typu byte (16×8 bitů) |
| fd 81 01 | i16×8.neg | otočení znaménka | 8 prvků typu word (8×16 bitů) |
| fd 8e 01 | i16×8.add | součet vektorů | 8 prvků typu word (8×16 bitů) |
| fd 91 01 | i16×8.sub | rozdíl vektorů | 8 prvků typu word (8×16 bitů) |
| fd a1 01 | i32×4.neg | otočení znaménka | 4 prvky typu double word (4×32 bitů) |
| fd ae 01 | i32×4.add | součet vektorů | 4 prvky typu double word (4×32 bitů) |
| fd b1 01 | i32×4.sub | rozdíl vektorů | 4 prvky typu double word (4×32 bitů) |
| fd c1 01 | i64×2.neg | otočení znaménka | 2 prvky typu quat word (2×64 bitů) |
| fd ce 01 | i64×2.add | součet vektorů | 2 prvky typu quat word (2×64 bitů) |
| fd d1 01 | i64×2.sub | rozdíl vektorů | 2 prvky typu quat word (2×64 bitů) |
| fd e1 01 | f32×4.neg | otočení znaménka | 4 prvky typu single (4×32 bitů) |
| fd e4 01 | f32×4.add | součet vektorů | 4 prvky typu single (4×32 bitů) |
| fd e5 01 | f32×4.sub | rozdíl vektorů | 4 prvky typu single (4×32 bitů) |
| fd ed 01 | f64×2.neg | otočení znaménka | 2 prvky typu double (2×64 bitů) |
| fd f0 01 | f64×2.add | součet vektorů | 2 prvky typu double (2×64 bitů) |
| fd f1 01 | f64×2.sub | rozdíl vektorů | 2 prvky typu double (2×64 bitů) |
6. Aritmetická operace součinu prvků vektorů
Další typickou aritmetickou operací je operace součinu odpovídajících si prvků vektorů (tedy nikoli vektorový součin). Tuto operaci vysvětlujeme v samostatné kapitole z toho důvodu, že není podporovaná pro všechny typy vektorů a z tohoto důvodu musí překladače do WebAssembly používat „rozepsání“ této operace do poměrně dlouhé sekvence instrukcí. Tuto vlastnost WebAssembly je nutné znát, protože v opačném případě by se mohlo stát, že přepisem algoritmu do vektorové podoby by se rychlost výsledného programu mohla (paradoxně) snížit.
Součin prvků vektorů prvek po prvku si otestujeme na příkladu, který vznikl nepatrnou úpravou původního demonstračního příkladu:
typedef signed char i8x16 __attribute__((vector_size(16)));
typedef unsigned char u8x16 __attribute__((vector_size(16)));
typedef signed short int i16x8 __attribute__((vector_size(16)));
typedef unsigned short int u16x8 __attribute__((vector_size(16)));
typedef signed int i32x4 __attribute__((vector_size(16)));
typedef unsigned int u32x4 __attribute__((vector_size(16)));
typedef signed long long int i64x2 __attribute__((vector_size(16)));
typedef unsigned long long int u64x2 __attribute__((vector_size(16)));
typedef float f32x4 __attribute__((vector_size(16)));
typedef double f64x2 __attribute__((vector_size(16)));
#define MUL(type) type mul_##type(type x, type y) {return (type)(x*y);}
#define ALL(type) \
MUL(type)
ALL(i8x16)
ALL(u8x16)
ALL(i16x8)
ALL(u16x8)
ALL(i32x4)
ALL(u32x4)
ALL(i64x2)
ALL(u64x2)
ALL(f32x4)
ALL(f64x2)
7. Překlad operace součinu prvků vektorů s využitím podporovaných instrukcí
Výsledek zpětného překladu z bajtkódu WebAssembly do čitelné podoby je poměrně rozsáhlý – jeho celková velikost dosahuje dvanácti kilobajtů! Je tomu tak z toho důvodu, že násobení vektorů prvek po prvku není podporováno pro všechny typy vektorů. Ukažme si prozatím výsledky pro podporované typy vektorů (a tedy pro existující instrukce WebAssembly):
000212 func[2] <mul_i16x8>: 000213: 20 01 | local.get 1 000215: 20 00 | local.get 0 000217: fd 95 01 | i16x8.mul 00021a: 0b | end 00021c func[3] <mul_u16x8>: 00021d: 20 01 | local.get 1 00021f: 20 00 | local.get 0 000221: fd 95 01 | i16x8.mul 000224: 0b | end 000226 func[4] <mul_i32x4>: 000227: 20 01 | local.get 1 000229: 20 00 | local.get 0 00022b: fd b5 01 | i32x4.mul 00022e: 0b | end 000230 func[5] <mul_u32x4>: 000231: 20 01 | local.get 1 000233: 20 00 | local.get 0 000235: fd b5 01 | i32x4.mul 000238: 0b | end 00023a func[6] <mul_i64x2>: 00023b: 20 01 | local.get 1 00023d: 20 00 | local.get 0 00023f: fd d5 01 | i64x2.mul 000242: 0b | end 000244 func[7] <mul_u64x2>: 000245: 20 01 | local.get 1 000247: 20 00 | local.get 0 000249: fd d5 01 | i64x2.mul 00024c: 0b | end 00024e func[8] <mul_f32x4>: 00024f: 20 00 | local.get 0 000251: 20 01 | local.get 1 000253: fd e6 01 | f32x4.mul 000256: 0b | end 000258 func[9] <mul_f64x2>: 000259: 20 00 | local.get 0 00025b: 20 01 | local.get 1 00025d: fd f2 01 | f64x2.mul 000260: 0b | end
8. Tabulka s instrukcemi pro součin odpovídajících si prvků vektorů
Všechny instrukce součinu prvků vektorů implementované ve WebAssembly jsou vypsány v následující tabulce. Opět zde můžeme vidět využití instrukčního prefixu fd a operační kódy instrukcí mají stejnou délku tří bajtů:
| Operační kód | Jméno instrukce | Struktura vektorů, které se násobí |
|---|---|---|
| fd 95 01 | i16×8.mul | 8 prvků typu word (8×16 bitů) |
| fd b5 01 | i32×4.mul | 4 prvky typu double word (4×32 bitů) |
| fd d5 01 | i64×2.mul | 2 prvky typu quad word (2×64 bitů) |
| fd e6 01 | f32×4.mul | 4 prvky typu single (4×32 bitů) |
| fd f2 01 | f64×2.mul | 2 prvky typu double (2×64 bitů) |
9. Způsob překladu součinu prvků vektorů typu byte
V případě, že se pokusíme o realizaci vynásobení odpovídajících si prvků vektorů typu byte, bude výsledek překladu do bajtkódu WebAssembly vypadat zcela odlišně od překladu, který jsme mohli vidět v sedmé kapitole. Je tomu tak z toho důvodu, že chybí příslušná SIMD instrukce, která by se měla jmenovat i8×16.mul. Překladač tedy musí provádět násobení skutečně prvek po prvku, což nebude příliš rychlé. Ve výsledném bajtkódu můžeme vidět opakující se sekvenci instrukcí:
local.get 1 i8x16.extract_lane_u n (pro n=1..15) local.get 0 i8x16.extract_lane_u n (pro n=1..15) i32.mul i8x16.replace_lane n (pro n=1..15)
Výjimkou je vynásobení nejnižších prvků, kde se namísto replace_lane použije instrukce splat:
local.get 1 i8x16.extract_lane_u 0 local.get 0 i8x16.extract_lane_u 0 i32.mul i8x16.splat
Celá funkce mul_i8×16 přeložená do bajtkódu bude vypadat následovně:
00004d func[0] <mul_i8x16>: 00004e: 20 01 | local.get 1 000050: fd 16 00 | i8x16.extract_lane_u 0 000053: 20 00 | local.get 0 000055: fd 16 00 | i8x16.extract_lane_u 0 000058: 6c | i32.mul 000059: fd 0f | i8x16.splat 00005b: 20 01 | local.get 1 00005d: fd 16 01 | i8x16.extract_lane_u 1 000060: 20 00 | local.get 0 000062: fd 16 01 | i8x16.extract_lane_u 1 000065: 6c | i32.mul 000066: fd 17 01 | i8x16.replace_lane 1 000069: 20 01 | local.get 1 00006b: fd 16 02 | i8x16.extract_lane_u 2 00006e: 20 00 | local.get 0 000070: fd 16 02 | i8x16.extract_lane_u 2 000073: 6c | i32.mul 000074: fd 17 02 | i8x16.replace_lane 2 000077: 20 01 | local.get 1 000079: fd 16 03 | i8x16.extract_lane_u 3 00007c: 20 00 | local.get 0 00007e: fd 16 03 | i8x16.extract_lane_u 3 000081: 6c | i32.mul 000082: fd 17 03 | i8x16.replace_lane 3 000085: 20 01 | local.get 1 000087: fd 16 04 | i8x16.extract_lane_u 4 00008a: 20 00 | local.get 0 00008c: fd 16 04 | i8x16.extract_lane_u 4 00008f: 6c | i32.mul 000090: fd 17 04 | i8x16.replace_lane 4 000093: 20 01 | local.get 1 000095: fd 16 05 | i8x16.extract_lane_u 5 000098: 20 00 | local.get 0 00009a: fd 16 05 | i8x16.extract_lane_u 5 00009d: 6c | i32.mul 00009e: fd 17 05 | i8x16.replace_lane 5 0000a1: 20 01 | local.get 1 0000a3: fd 16 06 | i8x16.extract_lane_u 6 0000a6: 20 00 | local.get 0 0000a8: fd 16 06 | i8x16.extract_lane_u 6 0000ab: 6c | i32.mul 0000ac: fd 17 06 | i8x16.replace_lane 6 0000af: 20 01 | local.get 1 0000b1: fd 16 07 | i8x16.extract_lane_u 7 0000b4: 20 00 | local.get 0 0000b6: fd 16 07 | i8x16.extract_lane_u 7 0000b9: 6c | i32.mul 0000ba: fd 17 07 | i8x16.replace_lane 7 0000bd: 20 01 | local.get 1 0000bf: fd 16 08 | i8x16.extract_lane_u 8 0000c2: 20 00 | local.get 0 0000c4: fd 16 08 | i8x16.extract_lane_u 8 0000c7: 6c | i32.mul 0000c8: fd 17 08 | i8x16.replace_lane 8 0000cb: 20 01 | local.get 1 0000cd: fd 16 09 | i8x16.extract_lane_u 9 0000d0: 20 00 | local.get 0 0000d2: fd 16 09 | i8x16.extract_lane_u 9 0000d5: 6c | i32.mul 0000d6: fd 17 09 | i8x16.replace_lane 9 0000d9: 20 01 | local.get 1 0000db: fd 16 0a | i8x16.extract_lane_u 10 0000de: 20 00 | local.get 0 0000e0: fd 16 0a | i8x16.extract_lane_u 10 0000e3: 6c | i32.mul 0000e4: fd 17 0a | i8x16.replace_lane 10 0000e7: 20 01 | local.get 1 0000e9: fd 16 0b | i8x16.extract_lane_u 11 0000ec: 20 00 | local.get 0 0000ee: fd 16 0b | i8x16.extract_lane_u 11 0000f1: 6c | i32.mul 0000f2: fd 17 0b | i8x16.replace_lane 11 0000f5: 20 01 | local.get 1 0000f7: fd 16 0c | i8x16.extract_lane_u 12 0000fa: 20 00 | local.get 0 0000fc: fd 16 0c | i8x16.extract_lane_u 12 0000ff: 6c | i32.mul 000100: fd 17 0c | i8x16.replace_lane 12 000103: 20 01 | local.get 1 000105: fd 16 0d | i8x16.extract_lane_u 13 000108: 20 00 | local.get 0 00010a: fd 16 0d | i8x16.extract_lane_u 13 00010d: 6c | i32.mul 00010e: fd 17 0d | i8x16.replace_lane 13 000111: 20 01 | local.get 1 000113: fd 16 0e | i8x16.extract_lane_u 14 000116: 20 00 | local.get 0 000118: fd 16 0e | i8x16.extract_lane_u 14 00011b: 6c | i32.mul 00011c: fd 17 0e | i8x16.replace_lane 14 00011f: 20 01 | local.get 1 000121: fd 16 0f | i8x16.extract_lane_u 15 000124: 20 00 | local.get 0 000126: fd 16 0f | i8x16.extract_lane_u 15 000129: 6c | i32.mul 00012a: fd 17 0f | i8x16.replace_lane 15 00012d: 0b | end
10. Aritmetická operace podílu prvků vektorů
Z pohledu implementace ve WebAssembly je nejvíce problematická operace podílu prováděná prvek po prvku. Tato operace totiž není podporována pro všechny typy vektorů, což se může negativně projevit na výkonu některých aplikací, které například provádí váhování, normalizaci atd. Ovšem nejdříve si zopakujme, tak bude vypadat zdrojový kód demonstračního příkladu, který po svém překladu vygeneruje WebAssembly pro celkem deset variant funkcí pro podíl odpovídajících si prvků vektorů:
typedef signed char i8x16 __attribute__((vector_size(16)));
typedef unsigned char u8x16 __attribute__((vector_size(16)));
typedef signed short int i16x8 __attribute__((vector_size(16)));
typedef unsigned short int u16x8 __attribute__((vector_size(16)));
typedef signed int i32x4 __attribute__((vector_size(16)));
typedef unsigned int u32x4 __attribute__((vector_size(16)));
typedef signed long long int i64x2 __attribute__((vector_size(16)));
typedef unsigned long long int u64x2 __attribute__((vector_size(16)));
typedef float f32x4 __attribute__((vector_size(16)));
typedef double f64x2 __attribute__((vector_size(16)));
#define DIV(type) type div_##type(type x, type y) {return (type)(x/y);}
#define ALL(type) \
DIV(type)
ALL(i8x16)
ALL(u8x16)
ALL(i16x8)
ALL(u16x8)
ALL(i32x4)
ALL(u32x4)
ALL(i64x2)
ALL(u64x2)
ALL(f32x4)
ALL(f64x2)
11. Překlad operace podílu prvků vektorů s využitím podporovaných instrukcí
Současné verze WebAssembly podporují pouze omezenou možnost výpočtu podílů odpovídajících si prvků vektorů. Velmi dobře je tato operace podporována pro vektory obsahující čtyři prvky typu float:
0003a6 func[8] <div_f32x4>: 0003a7: 20 00 | local.get 0 0003a9: 20 01 | local.get 1 0003ab: fd e7 01 | f32x4.div 0003ae: 0b | end
Totéž platí pro vektory obsahující dva prvky typu double:
0003b0 func[9] <div_f64x2>: 0003b1: 20 00 | local.get 0 0003b3: 20 01 | local.get 1 0003b5: fd f3 01 | f64x2.div 0003b8: 0b | end
Ovšem pro vektory obsahující celočíselné hodnoty je již nutné provést výpočet podílu explicitně prvek po prvku (a tedy dosti neefektivním způsobem). Příkladem může být podíl 32bitových celočíselných prvků bez znaménka:
000330 func[5] <div_u32x4>: 000331: 20 00 | local.get 0 000333: fd 1b 00 | i32x4.extract_lane 0 000336: 20 01 | local.get 1 000338: fd 1b 00 | i32x4.extract_lane 0 00033b: 6e | i32.div_u 00033c: fd 11 | i32x4.splat 00033e: 20 00 | local.get 0 000340: fd 1b 01 | i32x4.extract_lane 1 000343: 20 01 | local.get 1 000345: fd 1b 01 | i32x4.extract_lane 1 000348: 6e | i32.div_u 000349: fd 1c 01 | i32x4.replace_lane 1 00034c: 20 00 | local.get 0 00034e: fd 1b 02 | i32x4.extract_lane 2 000351: 20 01 | local.get 1 000353: fd 1b 02 | i32x4.extract_lane 2 000356: 6e | i32.div_u 000357: fd 1c 02 | i32x4.replace_lane 2 00035a: 20 00 | local.get 0 00035c: fd 1b 03 | i32x4.extract_lane 3 00035f: 20 01 | local.get 1 000361: fd 1b 03 | i32x4.extract_lane 3 000364: 6e | i32.div_u 000365: fd 1c 03 | i32x4.replace_lane 3 000368: 0b | end
Totéž platí pro vektory obsahující dva 64bitové prvky bez znaménka:
000388 func[7] <div_u64x2>: 000389: 20 00 | local.get 0 00038b: fd 1d 00 | i64x2.extract_lane 0 00038e: 20 01 | local.get 1 000390: fd 1d 00 | i64x2.extract_lane 0 000393: 80 | i64.div_u 000394: fd 12 | i64x2.splat 000396: 20 00 | local.get 0 000398: fd 1d 01 | i64x2.extract_lane 1 00039b: 20 01 | local.get 1 00039d: fd 1d 01 | i64x2.extract_lane 1 0003a0: 80 | i64.div_u 0003a1: fd 1e 01 | i64x2.replace_lane 1 0003a4: 0b | end
12. Tabulka s instrukcemi pro podíl odpovídajících si prvků vektorů
V bajtkódu funkcí přeložených do WebAssembly, který jsme si ukázali v předchozí kapitole, byly přítomny pouze dvě nové SIMD instrukce, a to konkrétně instrukce f32×4.div a f64×2.div. Tyto instrukce jsou pro úplnost uvedeny v následující tabulce:
| Operační kód | Jméno instrukce | Struktura vektorů, které se dělí |
|---|---|---|
| fd e7 01 | f32×4.div | 4 prvky typu single (4×32 bitů) |
| fd f3 01 | f64×2.div | 2 prvky typu double (2×64 bitů) |
13. Instrukce pro konstrukci nového vektoru
V předchozích demonstračních příkladech jsme se setkali s instrukcí nazvanou splat. Jedná se o instrukci, která z jediné skalární hodnoty vytvoří celý vektor. Typ vektoru je určen prefixem instrukce a od typu vektoru se pochopitelně odvozuje i počet jeho prvků. Těchto instrukcí existuje celkem šest. Není totiž zapotřebí rozlišovat mezi typy se znaménkem a typy bez znaménka:
| Operační kód | Jméno instrukce | Struktura vektorů, které se konstruují |
|---|---|---|
| fd 0f | i8×16.splat | 16 prvků typu byte (16×8 bitů) |
| fd 10 | i16×8.splat | 8 prvků typu word (8×16 bitů) |
| fd 11 | i32×4.splat | 4 prvky typu double word (4×32 bitů) |
| fd 12 | i64×2.splat | 2 prvky typu quad word (2×64 bitů) |
| fd 13 | f32×4.splat | 4 prvky typu single (4×32 bitů) |
| fd 14 | f64×2.splat | 2 prvky typu double (2×64 bitů) |
Kromě toho existují i instrukce typu load, které jsou nepatrně komplikovanější a seznámíme se s nimi příště.
14. Instrukce pro extrakci prvku i pro zpětný zápis prvku do vektoru
Další sada osmi instrukcí slouží k přečtení hodnoty jediného prvku z vektoru. Index čteného prvku je uložen ve třetím bajtu tvořícím operační kód instrukce:
| Operační kód | Jméno instrukce | Struktura vektorů |
|---|---|---|
| fd 15 | i8×16.extract_lane_s | 16 prvků typu byte (16×8 bitů) |
| fd 16 | i8×16.extract_lane_u | 16 prvků typu byte (16×8 bitů) |
| fd 18 | i16×8.extract_lane_s | 8 prvků typu word (8×16 bitů) |
| fd 19 | i16×8.extract_lane_u | 8 prvků typu word (8×16 bitů) |
| fd 1b | i32×4.extract_lane | 4 prvky typu double word (4×32 bitů) |
| fd 1d | i64×2.extract_lane | 2 prvky typu quad word (2×64 bitů) |
| fd 1f | f32×4.extract_lane | 4 prvky typu single (4×32 bitů) |
| fd 21 | f64×2.extract_lane | 2 prvky typu double (2×64 bitů) |
Ve WebAssembly existují i instrukce s opačným významem, konkrétně takové instrukce, které ve vektoru změní jediný prvek. Skalární hodnota, která má být do prvku uložena, musí být umístěna na zásobníku, index modifikovaného prvku je opět uložen ve třetím bajtu tvořícím operační kód instrukce:
| Operační kód | Jméno instrukce | Struktura vektorů |
|---|---|---|
| fd 17 | i8×16.replace_lane | 16 prvků typu byte (16×8 bitů) |
| fd 1a | i16×8.replace_lane | 8 prvků typu word (8×16 bitů) |
| fd 1c | i32×4.replace_lane | 4 prvky typu double word (4×32 bitů) |
| fd 1e | i64×2.replace_lane | 2 prvky typu quad word (2×64 bitů) |
| fd 20 | f32×4.replace_lane | 4 prvky typu single (4×32 bitů) |
| fd 22 | f64×2.replace_lane | 2 prvky typu double (2×64 bitů) |
15. Překlad složitějších vektorových operací: výpočet skalárního součinu dvou vektorů
S rozvojem neuronových sítí a umělé inteligence (ale nejenom zde) se začal masivně využívat známý algoritmus pro výpočet skalárního součinu (dot product, scalar product). Tento algoritmus se používá v oblasti velkých jazykových modelů (LLM) pro zjišťování podobnosti dlouhých vektorů s numerickými hodnotami (vector similarity). Kromě klasického skalárního součinu se v této oblasti používá i tzv. cosinus similarity, což je varianta skalárního součinu, v níž nezáleží na délce vektorů, ale pouze na jejich vzájemné orientaci (výpočet je tedy doplněn o normalizaci vektorů). A toto porovnávání vektorů se v LLM provádí neustále a většinou je optimalizováno a výpočty běží na GPU.
To však není zdaleka vše. Pokud se zaměříme na oblast klasických neuronových sítí (NN – neural networks), zjistíme, že se tyto sítě skládají z takzvaných perceptronů, což je vlastně značně zjednodušený model neuronu. A na vstup perceptronu se přivádí nějaké množství numerických vstupů. Každý z těchto vstupů je váhován, tj. vynásoben určitou konstantou a výsledky tohoto váhování jsou nakonec sečteny. Když se ovšem nad touto operací zamyslíme, zjistíme, že se vlastně nejedná o nic jiného, než o aplikaci výpočtu skalárního součinu. První z vektorů, který do tohoto součinu vstupuje jako operand, jsou vstupy do neuronu, druhým vektorem je pak vektor vah, které si neuron zapamatoval. A samotný trénink neuronové sítě vlastně není nic jiného, než rekonfigurace těchto vah – vektorů:
Vyzkoušejme si, jakým způsobem se přeloží operace skalárního součinu dvou vektorů, které obsahují buď čtyři prvky typu float nebo osm prvků typu double. Realizace těchto operací v jazyku C je jednoduchá:
typedef float f32x4 __attribute__((vector_size(16)));
typedef double f64x2 __attribute__((vector_size(16)));
float dot_float(f32x4 x, f32x4 y) {
return x[0]*y[0]+x[1]*y[1]+x[2]*y[2]+x[3]*y[3];
}
double dot_double(f64x2 x, f64x2 y) {
return x[0]*y[0]+x[1]*y[1];
}
Výsledek překladu první z těchto funkcí do bajtkódu ukazuje, že se pro výpočet nepoužila žádná specializovaná instrukce určená pro výpočet skalárního součinu, ale provedl se explicitní součin odpovídajících si prvků, za nimiž následuje součet všech čtyř mezivýsledků:
00004a func[0] <dot_float>: 00004b: 20 00 | local.get 0 00004d: fd 1f 03 | f32x4.extract_lane 3 000050: 20 01 | local.get 1 000052: fd 1f 03 | f32x4.extract_lane 3 000055: 94 | f32.mul 000056: 20 00 | local.get 0 000058: fd 1f 02 | f32x4.extract_lane 2 00005b: 20 01 | local.get 1 00005d: fd 1f 02 | f32x4.extract_lane 2 000060: 94 | f32.mul 000061: 20 00 | local.get 0 000063: fd 1f 00 | f32x4.extract_lane 0 000066: 20 01 | local.get 1 000068: fd 1f 00 | f32x4.extract_lane 0 00006b: 94 | f32.mul 00006c: 20 00 | local.get 0 00006e: 20 01 | local.get 1 000070: fd e6 01 | f32x4.mul 000073: fd 1f 01 | f32x4.extract_lane 1 000076: 92 | f32.add 000077: 92 | f32.add 000078: 92 | f32.add 000079: 0b | end
Totéž platí i pro druhou funkci. Zde se (logicky) provádí jen polovina výpočtů, protože vektory, které skalárně násobíme, mají poloviční počet prvků:
00007b func[1] <dot_double>: 00007c: 20 00 | local.get 0 00007e: fd 21 00 | f64x2.extract_lane 0 000081: 20 01 | local.get 1 000083: fd 21 00 | f64x2.extract_lane 0 000086: a2 | f64.mul 000087: 20 00 | local.get 0 000089: 20 01 | local.get 1 00008b: fd f2 01 | f64x2.mul 00008e: fd 21 01 | f64x2.extract_lane 1 000091: a0 | f64.add 000092: 0b | end
16. Výpočet druhé odmocniny prvků vektorů typu float a double
Další často používanou operací, například při zpracování signálů nebo v počítačové grafice, je výpočet druhé odmocniny všech prvků vektoru. Opět si ukážeme, jakým způsobem je tato operace přeložena do WebAssembly v případě, že vektory budou obsahovat prvky typu float nebo double (pro celočíselné prvky se druhá odmocnina počítá méně často a neexistují pro ni ani odpovídající instrukce WebAssembly):
float sqrtf(float x);
double sqrt(double x);
typedef float f32x4 __attribute__((vector_size(16)));
typedef double f64x2 __attribute__((vector_size(16)));
f32x4 vector_sqrt_f32x4(f32x4 x) {
f32x4 result;
int i;
for (i=0; i<4; i++) {
result[i] = sqrtf(x[i]);
}
return result;
}
f64x2 vector_sqrt_f64x2(f64x2 x) {
f64x2 result;
int i;
for (i=0; i<2; i++) {
result[i] = sqrt(x[i]);
}
return result;
}
Výsledek překladu první funkce do bajtkódu WebAssembly ukazuje, že se skutečně použila specializovaná instrukce pro výpočet druhé odmocniny prvků vektorů typu f32×4:
000043 func[0] <vector_sqrt_f32x4>: 000044: 20 00 | local.get 0 000046: fd e3 01 | f32x4.sqrt 000049: 0b | end
V případě druhé funkce se použila opět instrukce sqrt, ovšem tentokrát s prefixem f64×2:
00004b func[1] <vector_sqrt_f64x2>: 00004c: 20 00 | local.get 0 00004e: fd ef 01 | f64x2.sqrt 000051: 0b | end
Zjistili jsme tedy, že v bajtkódu WebAssembly jsou podporovány další dvě vektorové instrukce:
| Operační kód | Jméno instrukce | Struktura vektorů, pro které se počítá druhá odmocnina |
|---|---|---|
| fd e3 01 | f32×4.sqrt | 4 prvky typu single (4×32 bitů) |
| fd ef 01 | f64×2.sqrt | 2 prvky typu double (2×64 bitů) |
17. Instrukce sloužící pro porovnání odpovídajících si prvků vektorů
Poslední sadou instrukcí WebAssembly, s níž se dnes seznámíme, jsou instrukce, které porovnají odpovídající si prvky dvou vektorů na zvolenou relaci. Výsledkem těchto operací přitom bude nový vektor obsahující pouze jedničky a nuly. V případě, že prvky zvolené relaci (například „menší než“) odpovídají, bude ve výsledném vektoru zapsána jednička, v opačném případě nula. Ukažme si jednoduchý příklad:
v1: [1, 2, 3, 4] v2: [1, 1, 4, 4] v1==v2: [1, 0, 0, 1] v1>v2: [0, 1, 0, 0]
Existuje celkem šest testů na relaci: rovnost, nerovnost, menší než, menší nebo rovno, větší než a větší nebo rovno. Teoreticky by mělo být možné porovnat prvky vektorů libovolných typů, přičemž celé porovnání by bylo realizováno jedinou instrukcí. Ovšem jak uvidíme po překladu dalšího příkladu, některé instrukce v současné verzi WebAssembly chybí a tudíž bude porovnání prvků vektorů provedeno pomalu – prvek po prvku.
V dnešním posledním demonstračním příkladu si ověříme, které porovnání vektorů bude realizováno jedinou instrukcí a které nikoli (tedy bude se muset provést porovnání prvek po prvku):
typedef signed char i8x16 __attribute__((vector_size(16)));
typedef unsigned char u8x16 __attribute__((vector_size(16)));
typedef signed short int i16x8 __attribute__((vector_size(16)));
typedef unsigned short int u16x8 __attribute__((vector_size(16)));
typedef signed int i32x4 __attribute__((vector_size(16)));
typedef unsigned int u32x4 __attribute__((vector_size(16)));
typedef signed long long int i64x2 __attribute__((vector_size(16)));
typedef unsigned long long int u64x2 __attribute__((vector_size(16)));
typedef float f32x4 __attribute__((vector_size(16)));
typedef double f64x2 __attribute__((vector_size(16)));
#define EQ(type) type eq_##type(type x, type y) {return x==y;}
#define LT(type) type lt_##type(type x, type y) {return x<y;}
#define LE(type) type le_##type(type x, type y) {return x<=y;}
#define GT(type) type gt_##type(type x, type y) {return x>y;}
#define GE(type) type ge_##type(type x, type y) {return x>=y;}
#define NE(type) type ne_##type(type x, type y) {return x!=y;}
#define ALL(type) \
EQ(type) \
LT(type) \
LE(type) \
GT(type) \
GE(type) \
NE(type)
ALL(i8x16)
ALL(u8x16)
ALL(i16x8)
ALL(u16x8)
ALL(i32x4)
ALL(u32x4)
ALL(i64x2)
ALL(u64x2)
ALL(f32x4)
ALL(f64x2)
Porovnání, které lze realizovat jedinou instrukcí:
00007e func[0] <eq_i8x16>: 00007f: 20 00 | local.get 0 000081: 20 01 | local.get 1 000083: fd 23 | i8x16.eq 000085: 0b | end 000087 func[1] <lt_i8x16>: 000088: 20 00 | local.get 0 00008a: 20 01 | local.get 1 00008c: fd 25 | i8x16.lt_s 00008e: 0b | end 000090 func[2] <le_i8x16>: 000091: 20 00 | local.get 0 000093: 20 01 | local.get 1 000095: fd 29 | i8x16.le_s 000097: 0b | end 000099 func[3] <gt_i8x16>: 00009a: 20 00 | local.get 0 00009c: 20 01 | local.get 1 00009e: fd 27 | i8x16.gt_s 0000a0: 0b | end 0000a2 func[4] <ge_i8x16>: 0000a3: 20 00 | local.get 0 0000a5: 20 01 | local.get 1 0000a7: fd 2b | i8x16.ge_s 0000a9: 0b | end 0000ab func[5] <ne_i8x16>: 0000ac: 20 00 | local.get 0 0000ae: 20 01 | local.get 1 0000b0: fd 24 | i8x16.ne 0000b2: 0b | end 0000b4 func[6] <eq_u8x16>: 0000b5: 20 00 | local.get 0 0000b7: 20 01 | local.get 1 0000b9: fd 23 | i8x16.eq 0000bb: 0b | end 0000bd func[7] <lt_u8x16>: 0000be: 20 00 | local.get 0 0000c0: 20 01 | local.get 1 0000c2: fd 26 | i8x16.lt_u 0000c4: 0b | end 0000c6 func[8] <le_u8x16>: 0000c7: 20 00 | local.get 0 0000c9: 20 01 | local.get 1 0000cb: fd 2a | i8x16.le_u 0000cd: 0b | end 0000cf func[9] <gt_u8x16>: 0000d0: 20 00 | local.get 0 0000d2: 20 01 | local.get 1 0000d4: fd 28 | i8x16.gt_u 0000d6: 0b | end 0000d8 func[10] <ge_u8x16>: 0000d9: 20 00 | local.get 0 0000db: 20 01 | local.get 1 0000dd: fd 2c | i8x16.ge_u 0000df: 0b | end 0000e1 func[11] <ne_u8x16>: 0000e2: 20 00 | local.get 0 0000e4: 20 01 | local.get 1 0000e6: fd 24 | i8x16.ne 0000e8: 0b | end 0000ea func[12] <eq_i16x8>: 0000eb: 20 00 | local.get 0 0000ed: 20 01 | local.get 1 0000ef: fd 2d | i16x8.eq 0000f1: 0b | end 0000f3 func[13] <lt_i16x8>: 0000f4: 20 00 | local.get 0 0000f6: 20 01 | local.get 1 0000f8: fd 2f | i16x8.lt_s 0000fa: 0b | end 0000fc func[14] <le_i16x8>: 0000fd: 20 00 | local.get 0 0000ff: 20 01 | local.get 1 000101: fd 33 | i16x8.le_s 000103: 0b | end 000105 func[15] <gt_i16x8>: 000106: 20 00 | local.get 0 000108: 20 01 | local.get 1 00010a: fd 31 | i16x8.gt_s 00010c: 0b | end 00010e func[16] <ge_i16x8>: 00010f: 20 00 | local.get 0 000111: 20 01 | local.get 1 000113: fd 35 | i16x8.ge_s 000115: 0b | end 000117 func[17] <ne_i16x8>: 000118: 20 00 | local.get 0 00011a: 20 01 | local.get 1 00011c: fd 2e | i16x8.ne 00011e: 0b | end 000120 func[18] <eq_u16x8>: 000121: 20 00 | local.get 0 000123: 20 01 | local.get 1 000125: fd 2d | i16x8.eq 000127: 0b | end 000129 func[19] <lt_u16x8>: 00012a: 20 00 | local.get 0 00012c: 20 01 | local.get 1 00012e: fd 30 | i16x8.lt_u 000130: 0b | end 000132 func[20] <le_u16x8>: 000133: 20 00 | local.get 0 000135: 20 01 | local.get 1 000137: fd 34 | i16x8.le_u 000139: 0b | end 00013b func[21] <gt_u16x8>: 00013c: 20 00 | local.get 0 00013e: 20 01 | local.get 1 000140: fd 32 | i16x8.gt_u 000142: 0b | end 000144 func[22] <ge_u16x8>: 000145: 20 00 | local.get 0 000147: 20 01 | local.get 1 000149: fd 36 | i16x8.ge_u 00014b: 0b | end 00014d func[23] <ne_u16x8>: 00014e: 20 00 | local.get 0 000150: 20 01 | local.get 1 000152: fd 2e | i16x8.ne 000154: 0b | end 000156 func[24] <eq_i32x4>: 000157: 20 00 | local.get 0 000159: 20 01 | local.get 1 00015b: fd 37 | i32x4.eq 00015d: 0b | end 00015f func[25] <lt_i32x4>: 000160: 20 00 | local.get 0 000162: 20 01 | local.get 1 000164: fd 39 | i32x4.lt_s 000166: 0b | end 000168 func[26] <le_i32x4>: 000169: 20 00 | local.get 0 00016b: 20 01 | local.get 1 00016d: fd 3d | i32x4.le_s 00016f: 0b | end 000171 func[27] <gt_i32x4>: 000172: 20 00 | local.get 0 000174: 20 01 | local.get 1 000176: fd 3b | i32x4.gt_s 000178: 0b | end 00017a func[28] <ge_i32x4>: 00017b: 20 00 | local.get 0 00017d: 20 01 | local.get 1 00017f: fd 3f | i32x4.ge_s 000181: 0b | end 000183 func[29] <ne_i32x4>: 000184: 20 00 | local.get 0 000186: 20 01 | local.get 1 000188: fd 38 | i32x4.ne 00018a: 0b | end 00018c func[30] <eq_u32x4>: 00018d: 20 00 | local.get 0 00018f: 20 01 | local.get 1 000191: fd 37 | i32x4.eq 000193: 0b | end 000195 func[31] <lt_u32x4>: 000196: 20 00 | local.get 0 000198: 20 01 | local.get 1 00019a: fd 3a | i32x4.lt_u 00019c: 0b | end 00019e func[32] <le_u32x4>: 00019f: 20 00 | local.get 0 0001a1: 20 01 | local.get 1 0001a3: fd 3e | i32x4.le_u 0001a5: 0b | end 0001a7 func[33] <gt_u32x4>: 0001a8: 20 00 | local.get 0 0001aa: 20 01 | local.get 1 0001ac: fd 3c | i32x4.gt_u 0001ae: 0b | end 0001b0 func[34] <ge_u32x4>: 0001b1: 20 00 | local.get 0 0001b3: 20 01 | local.get 1 0001b5: fd 40 | i32x4.ge_u 0001b7: 0b | end 0001b9 func[35] <ne_u32x4>: 0001ba: 20 00 | local.get 0 0001bc: 20 01 | local.get 1 0001be: fd 38 | i32x4.ne 0001c0: 0b | end 0001c2 func[36] <eq_i64x2>: 0001c3: 20 00 | local.get 0 0001c5: 20 01 | local.get 1 0001c7: fd d6 01 | i64x2.eq 0001ca: 0b | end 0001cc func[37] <lt_i64x2>: 0001cd: 20 00 | local.get 0 0001cf: 20 01 | local.get 1 0001d1: fd d8 01 | i64x2.lt_s 0001d4: 0b | end 0001d6 func[38] <le_i64x2>: 0001d7: 20 00 | local.get 0 0001d9: 20 01 | local.get 1 0001db: fd da 01 | i64x2.le_s 0001de: 0b | end 0001e0 func[39] <gt_i64x2>: 0001e1: 20 00 | local.get 0 0001e3: 20 01 | local.get 1 0001e5: fd d9 01 | i64x2.gt_s 0001e8: 0b | end 0001ea func[40] <ge_i64x2>: 0001eb: 20 00 | local.get 0 0001ed: 20 01 | local.get 1 0001ef: fd db 01 | i64x2.ge_s 0001f2: 0b | end 0001f4 func[41] <ne_i64x2>: 0001f5: 20 00 | local.get 0 0001f7: 20 01 | local.get 1 0001f9: fd d7 01 | i64x2.ne 0001fc: 0b | end 0001fe func[42] <eq_u64x2>: 0001ff: 20 00 | local.get 0 000201: 20 01 | local.get 1 000203: fd d6 01 | i64x2.eq 000206: 0b | end 0002a8 func[47] <ne_u64x2>: 0002a9: 20 00 | local.get 0 0002ab: 20 01 | local.get 1 0002ad: fd d7 01 | i64x2.ne 0002b0: 0b | end 0002b2 func[48] <eq_f32x4>: 0002b3: 20 00 | local.get 0 0002b5: 20 01 | local.get 1 0002b7: fd 41 | f32x4.eq 0002b9: 0b | end 0002bb func[49] <lt_f32x4>: 0002bc: 20 00 | local.get 0 0002be: 20 01 | local.get 1 0002c0: fd 43 | f32x4.lt 0002c2: 0b | end 0002c4 func[50] <le_f32x4>: 0002c5: 20 00 | local.get 0 0002c7: 20 01 | local.get 1 0002c9: fd 45 | f32x4.le 0002cb: 0b | end 0002cd func[51] <gt_f32x4>: 0002ce: 20 00 | local.get 0 0002d0: 20 01 | local.get 1 0002d2: fd 44 | f32x4.gt 0002d4: 0b | end 0002d6 func[52] <ge_f32x4>: 0002d7: 20 00 | local.get 0 0002d9: 20 01 | local.get 1 0002db: fd 46 | f32x4.ge 0002dd: 0b | end 0002df func[53] <ne_f32x4>: 0002e0: 20 00 | local.get 0 0002e2: 20 01 | local.get 1 0002e4: fd 42 | f32x4.ne 0002e6: 0b | end 0002e8 func[54] <eq_f64x2>: 0002e9: 20 00 | local.get 0 0002eb: 20 01 | local.get 1 0002ed: fd 47 | f64x2.eq 0002ef: 0b | end 0002f1 func[55] <lt_f64x2>: 0002f2: 20 00 | local.get 0 0002f4: 20 01 | local.get 1 0002f6: fd 49 | f64x2.lt 0002f8: 0b | end 0002fa func[56] <le_f64x2>: 0002fb: 20 00 | local.get 0 0002fd: 20 01 | local.get 1 0002ff: fd 4b | f64x2.le 000301: 0b | end 000303 func[57] <gt_f64x2>: 000304: 20 00 | local.get 0 000306: 20 01 | local.get 1 000308: fd 4a | f64x2.gt 00030a: 0b | end 00030c func[58] <ge_f64x2>: 00030d: 20 00 | local.get 0 00030f: 20 01 | local.get 1 000311: fd 4c | f64x2.ge 000313: 0b | end 000315 func[59] <ne_f64x2>: 000316: 20 00 | local.get 0 000318: 20 01 | local.get 1 00031a: fd 48 | f64x2.ne 00031c: 0b | end
Kupodivu však nejsou podporovány některé instrukce pro porovnání vektorů s prvky typu unsigned long long. Ovšem tyto vektory jsou velmi krátké (dva prvky), takže výsledný kód ve WebAssembly není příliš dlouhý:
000208 func[43] <lt_u64x2>: 000209: 42 7f | i64.const 18446744073709551615 00020b: 42 00 | i64.const 0 00020d: 20 00 | local.get 0 00020f: fd 1d 00 | i64x2.extract_lane 0 000212: 20 01 | local.get 1 000214: fd 1d 00 | i64x2.extract_lane 0 000217: 54 | i64.lt_u 000218: 1b | select 000219: fd 12 | i64x2.splat 00021b: 42 7f | i64.const 18446744073709551615 00021d: 42 00 | i64.const 0 00021f: 20 00 | local.get 0 000221: fd 1d 01 | i64x2.extract_lane 1 000224: 20 01 | local.get 1 000226: fd 1d 01 | i64x2.extract_lane 1 000229: 54 | i64.lt_u 00022a: 1b | select 00022b: fd 1e 01 | i64x2.replace_lane 1 00022e: 0b | end 000230 func[44] <le_u64x2>: 000231: 42 7f | i64.const 18446744073709551615 000233: 42 00 | i64.const 0 000235: 20 00 | local.get 0 000237: fd 1d 00 | i64x2.extract_lane 0 00023a: 20 01 | local.get 1 00023c: fd 1d 00 | i64x2.extract_lane 0 00023f: 58 | i64.le_u 000240: 1b | select 000241: fd 12 | i64x2.splat 000243: 42 7f | i64.const 18446744073709551615 000245: 42 00 | i64.const 0 000247: 20 00 | local.get 0 000249: fd 1d 01 | i64x2.extract_lane 1 00024c: 20 01 | local.get 1 00024e: fd 1d 01 | i64x2.extract_lane 1 000251: 58 | i64.le_u 000252: 1b | select 000253: fd 1e 01 | i64x2.replace_lane 1 000256: 0b | end 000258 func[45] <gt_u64x2>: 000259: 42 7f | i64.const 18446744073709551615 00025b: 42 00 | i64.const 0 00025d: 20 00 | local.get 0 00025f: fd 1d 00 | i64x2.extract_lane 0 000262: 20 01 | local.get 1 000264: fd 1d 00 | i64x2.extract_lane 0 000267: 56 | i64.gt_u 000268: 1b | select 000269: fd 12 | i64x2.splat 00026b: 42 7f | i64.const 18446744073709551615 00026d: 42 00 | i64.const 0 00026f: 20 00 | local.get 0 000271: fd 1d 01 | i64x2.extract_lane 1 000274: 20 01 | local.get 1 000276: fd 1d 01 | i64x2.extract_lane 1 000279: 56 | i64.gt_u 00027a: 1b | select 00027b: fd 1e 01 | i64x2.replace_lane 1 00027e: 0b | end 000280 func[46] <ge_u64x2>: 000281: 42 7f | i64.const 18446744073709551615 000283: 42 00 | i64.const 0 000285: 20 00 | local.get 0 000287: fd 1d 00 | i64x2.extract_lane 0 00028a: 20 01 | local.get 1 00028c: fd 1d 00 | i64x2.extract_lane 0 00028f: 5a | i64.ge_u 000290: 1b | select 000291: fd 12 | i64x2.splat 000293: 42 7f | i64.const 18446744073709551615 000295: 42 00 | i64.const 0 000297: 20 00 | local.get 0 000299: fd 1d 01 | i64x2.extract_lane 1 00029c: 20 01 | local.get 1 00029e: fd 1d 01 | i64x2.extract_lane 1 0002a1: 5a | i64.ge_u 0002a2: 1b | select 0002a3: fd 1e 01 | i64x2.replace_lane 1 0002a6: 0b | end
Shrňme si výsledky, které jsme právě získali, do tabulky:
| Operační kód | Jméno instrukce | Prováděná operace | Struktura vektorů, které se porovnávají |
|---|---|---|---|
| fd 23 | i8×16.eq | test na rovnost prvků | 16 prvků typu byte (16×8 bitů) |
| fd 24 | i8×16.ne | test na nerovnost prvků | 16 prvků typu byte (16×8 bitů) |
| fd 25 | i8×16.lt_s | relace „menší než“ | 16 prvků typu signed char (16×8 bitů) |
| fd 29 | i8×16.le_s | relace „menší nebo rovno“ | 16 prvků typu signed char (16×8 bitů) |
| fd 27 | i8×16.gt_s | relace „větší než“ | 16 prvků typu signed char (16×8 bitů) |
| fd 2b | i8×16.ge_s | relace „větší nebo rovno“ | 16 prvků typu signed char (16×8 bitů) |
| fd 26 | i8×16.lt_u | relace „menší než“ | 16 prvků typu unsigned char (16×8 bitů) |
| fd 2a | i8×16.le_u | relace „menší nebo rovno“ | 16 prvků typu unsigned char (16×8 bitů) |
| fd 28 | i8×16.gt_u | relace „větší než“ | 16 prvků typu unsigned char (16×8 bitů) |
| fd 2c | i8×16.ge_u | relace „větší nebo rovno“ | 16 prvků typu unsigned char (16×8 bitů) |
| fd 2d | i16×8.eq | test na rovnost prvků | 8 prvků typu word (8×16 bitů) |
| fd 2e | i16×8.ne | test na nerovnost prvků | 8 prvků typu word (8×16 bitů) |
| fd 2f | i16×8.lt_s | relace „menší než“ | 8 prvků typu signed word (8×16 bitů) |
| fd 33 | i16×8.le_s | relace „menší nebo rovno“ | 8 prvků typu signed word (8×16 bitů) |
| fd 31 | i16×8.gt_s | relace „větší než“ | 8 prvků typu signed word (8×16 bitů) |
| fd 35 | i16×8.ge_s | relace „větší nebo rovno“ | 8 prvků typu signed word (8×16 bitů) |
| fd 30 | i16×8.lt_u | relace „menší než“ | 8 prvků typu usigned word (8×16 bitů) |
| fd 34 | i16×8.le_u | relace „menší nebo rovno“ | 8 prvků typu usigned word (8×16 bitů) |
| fd 32 | i16×8.gt_u | relace „větší než“ | 8 prvků typu usigned word (8×16 bitů) |
| fd 36 | i16×8.ge_u | relace „větší nebo rovno“ | 8 prvků typu usigned word (8×16 bitů) |
| fd 37 | i32×4.eq | test na rovnost prvků | 4 prvky typu long (4×32 bitů) |
| fd 38 | i32×4.ne | test na nerovnost prvků | 4 prvky typu long (4×32 bitů) |
| fd 39 | i32×4.lt_s | relace „menší než“ | 4 prvky typu signed long (4×32 bitů) |
| fd 3d | i32×4.le_s | relace „menší nebo rovno“ | 4 prvky typu signed long (4×32 bitů) |
| fd 3b | i32×4.gt_s | relace „větší než“ | 4 prvky typu signed long (4×32 bitů) |
| fd 3f | i32×4.ge_s | relace „větší nebo rovno“ | 4 prvky typu signed long (4×32 bitů) |
| fd 3a | i32×4.lt_u | relace „menší než“ | 4 prvky typu usigned long (4×32 bitů) |
| fd 3e | i32×4.le_u | relace „menší nebo rovno“ | 4 prvky typu usigned long (4×32 bitů) |
| fd 3c | i32×4.gt_u | relace „větší než“ | 4 prvky typu usigned long (4×32 bitů) |
| fd 40 | i32×4.ge_u | relace „větší nebo rovno“ | 4 prvky typu usigned long (4×32 bitů) |
| fd d6 01 | i64×2.eq | test na rovnost prvků | 2 prvky typu long long (2×64 bitů) |
| fd d7 01 | i64×2.ne | test na nerovnost prvků | 2 prvky typu long long (2×64 bitů) |
| fd d8 01 | i64×2.lt_s | relace „menší než“ | 2 prvky typu long signed long (2×64 bitů) |
| fd da 01 | i64×2.le_s | relace „menší nebo rovno“ | 2 prvky typu long signed long (2×64 bitů) |
| fd d9 01 | i64×2.gt_s | relace „větší než“ | 2 prvky typu long signed long (2×64 bitů) |
| fd db 01 | i64×2.ge_s | relace „větší nebo rovno“ | 2 prvky typu long signed long (2×64 bitů) |
| fd 41 | f32×4.eq | test na rovnost prvků | 4 prvky typu float (4×32 bitů) |
| fd 43 | f32×4.lt | test na nerovnost prvků | 4 prvky typu float (4×32 bitů) |
| fd 45 | f32×4.le | relace „menší než“ | 4 prvky typu float (4×32 bitů) |
| fd 44 | f32×4.gt | relace „menší nebo rovno“ | 4 prvky typu float (4×32 bitů) |
| fd 46 | f32×4.ge | relace „větší než“ | 4 prvky typu float (4×32 bitů) |
| fd 42 | f32×4.ne | relace „větší nebo rovno“ | 4 prvky typu float (4×32 bitů) |
| fd 47 | f64×2.eq | test na rovnost prvků | 2 prvky typu double (2×64 bitů) |
| fd 49 | f64×2.lt | test na nerovnost prvků | 2 prvky typu double (2×64 bitů) |
| fd 4b | f64×2.le | relace „menší než“ | 2 prvky typu double (2×64 bitů) |
| fd 4a | f64×2.gt | relace „menší nebo rovno“ | 2 prvky typu double (2×64 bitů) |
| fd 4c | f64×2.ge | relace „větší než“ | 2 prvky typu double (2×64 bitů) |
| fd 48 | f64×2.ne | relace „větší nebo rovno“ | 2 prvky typu double (2×64 bitů) |
18. Tabulka se všemi doposud popsanými instrukcemi
Všechny instrukce WebAssembly, které jsme si až doposud popsali, jsou vypsány v následující tabulce, kde jsou seřazeny podle svého operačního kódu (tedy na základě hodnoty svého prvního bajtu). Aby bylo zřejmé, jaký rozsah instrukčního souboru již byl popsán, obsahuje tabulka i (prozatím) prázdné řádky:
| Operační kód | Jméno instrukce | Stručný popis |
|---|---|---|
| 0×00 | ||
| 0×01 | ||
| 0×02 | block | uloží na zásobník řízení toku (control-flow stack) |
| 0×03 | loop | návěští je nastavena na současnou pozici v bajtkódu a zapamatováno |
| 0×04 | ||
| 0×05 | ||
| 0×06 | ||
| 0×07 | ||
| 0×08 | ||
| 0×09 | ||
| 0×0a | ||
| 0×0b | end | konec bloku nebo konec celé funkce |
| 0×0c | br | provede se nepodmíněný skok |
| 0×0d | br_if | pokud je na zásobníku uložena nenulová hodnota, provede se skok, jinak se neprovede žádná operace |
| 0×0e | br_table | rozeskok mezi více bloky |
| 0×0f | return | ukončení funkce s předáním návratových hodnot přes zásobník |
| 0×10 | call | zavolání funkce |
| 0×11 | ||
| 0×12 | ||
| 0×13 | ||
| 0×14 | ||
| 0×15 | ||
| 0×16 | ||
| 0×17 | ||
| 0×18 | ||
| 0×19 | ||
| 0×1a | drop | odstranění hodnoty nebo hodnot ze zásobníku operandů |
| 0×1b | select | ze zásobníku přečte tři hodnoty, na základě výsledku podmínky vrátí na zásobník druhou nebo třetí hodnotu |
| 0×1c | ||
| 0×1d | ||
| 0×1e | ||
| 0×1f | ||
| 0×20 | local.get | uložení hodnoty lokální proměnné na zásobník |
| 0×21 | local.set | přenos hodnoty z vrcholu zásobníku do lokální proměnné |
| 0×22 | local.tee | kopie hodnoty z vrcholu zásobníku do lokální proměnné |
| 0×23 | ||
| 0×24 | ||
| 0×25 | ||
| 0×26 | ||
| 0×27 | ||
| 0×28 | ||
| 0×29 | ||
| 0×2a | ||
| 0×2b | ||
| 0×2c | ||
| 0×2d | ||
| 0×2e | ||
| 0×2f | ||
| 0×30 | ||
| 0×31 | ||
| 0×32 | ||
| 0×33 | ||
| 0×34 | ||
| 0×35 | ||
| 0×36 | ||
| 0×37 | ||
| 0×38 | ||
| 0×39 | ||
| 0×3a | ||
| 0×3b | ||
| 0×3c | ||
| 0×3d | ||
| 0×3e | ||
| 0×3f | ||
| 0×41 | i32.const | uložení 32bitové celočíselné konstanty na zásobník |
| 0×42 | i64.const | uložení 64bitové celočíselné konstanty na zásobník |
| 0×43 | f32.const | uložení 32bitové konstanty s plovoucí řádovou čárkou na zásobník |
| 0×44 | f64.const | uložení 64bitové konstanty s plovoucí řádovou čárkou na zásobník |
| 0×44 | ||
| 0×45 | ||
| 0×46 | i32.eq | porovnání operandů typu int32 na relaci „rovno“ |
| 0×47 | i32.ne | porovnání operandů typu int32 na relaci „nerovno“ |
| 0×48 | i32.lt_s | porovnání operandů typu int32 na relaci „menší než“ (se znaménkem) |
| 0×49 | i32.lt_u | porovnání operandů typu int32 na relaci „menší než“ (bez znaménka) |
| 0×4a | i32.gt_s | porovnání operandů typu int32 na relaci „větší než“ (se znaménkem) |
| 0×4b | i32.gt_u | porovnání operandů typu int32 na relaci „větší než“ (bez znaménka) |
| 0×4c | i32.le_s | porovnání operandů typu int32 na relaci „menší nebo rovno“ (se znaménkem) |
| 0×4d | i32.le_u | porovnání operandů typu int32 na relaci „menší nebo rovno“ (bez znaménka) |
| 0×4e | i32.ge_s | porovnání operandů typu int32 na relaci „větší nebo rovno“ (se znaménkem) |
| 0×4f | i32.ge_u | porovnání operandů typu int32 na relaci „větší nebo rovno“ (bez znaménka) |
| 0×50 | ||
| 0×51 | i64.eq | porovnání operandů typu int64 na relaci „rovno“ |
| 0×52 | i64.ne | porovnání operandů typu int64 na relaci „nerovno“ |
| 0×53 | i64.lt_s | porovnání operandů typu int64 na relaci „menší než“ (se znaménkem) |
| 0×54 | i64.lt_u | porovnání operandů typu int64 na relaci „menší než“ (bez znaménka) |
| 0×55 | i64.gt_s | porovnání operandů typu int64 na relaci „větší než“ (se znaménkem) |
| 0×56 | i64.gt_u | porovnání operandů typu int64 na relaci „větší než“ (bez znaménka) |
| 0×57 | i64.le_s | porovnání operandů typu int64 na relaci „menší nebo rovno“ (se znaménkem) |
| 0×58 | i64.le_u | porovnání operandů typu int64 na relaci „menší nebo rovno“ (bez znaménka) |
| 0×59 | i64.ge_s | porovnání operandů typu int64 na relaci „větší nebo rovno“ (se znaménkem) |
| 0×5a | i64.ge_u | porovnání operandů typu int64 na relaci „větší nebo rovno“ (bez znaménka) |
| 0×5b | f32.eq | porovnání dvou hodnot typu single na relaci „rovno“ |
| 0×5c | f32.ne | porovnání dvou hodnot typu single na relaci „nerovno“ |
| 0×5d | f32.lt | porovnání dvou hodnot typu single na relaci „menší než“ |
| 0×5e | f32.gt | porovnání dvou hodnot typu single na relaci „větší než“ |
| 0×5f | f32.le | porovnání dvou hodnot typu single na relaci „menší nebo rovno“ |
| 0×60 | f32.ge | porovnání dvou hodnot typu single na relaci „větší nebo rovno“ |
| 0×61 | f64.eq | porovnání dvou hodnot typu double na relaci „rovno“ |
| 0×62 | f64.ne | porovnání dvou hodnot typu double na relaci „nerovno“ |
| 0×63 | f64.lt | porovnání dvou hodnot typu double na relaci „menší než“ |
| 0×64 | f64.gt | porovnání dvou hodnot typu double na relaci „větší než“ |
| 0×65 | f64.le | porovnání dvou hodnot typu double na relaci „menší nebo rovno“ |
| 0×66 | f64.ge | porovnání dvou hodnot typu double na relaci „větší nebo rovno“ |
| 0×67 | ||
| 0×68 | ||
| 0×69 | ||
| 0×6a | i32.add | součet dvou celých 32bitových hodnot |
| 0×6b | i32.sub | rozdíl dvou celých 32bitových hodnot |
| 0×6c | i32.mul | součin dvou celých 32bitových hodnot |
| 0×6d | i32.div_s | podíl dvou celých 32bitových hodnot se znaménkem |
| 0×6e | i32.div_u | podíl dvou celých 32bitových hodnot bez znaménka |
| 0×6f | i32.rem_s | zbytek po dělení dvou celých 32bitových hodnot se znaménkem |
| 0×70 | i32.rem_u | zbytek po dělení dvou celých 32bitových hodnot bez znaménka |
| 0×71 | ||
| 0×72 | ||
| 0×73 | ||
| 0×74 | ||
| 0×75 | ||
| 0×76 | ||
| 0×77 | ||
| 0×78 | ||
| 0×79 | ||
| 0×7a | ||
| 0×7b | ||
| 0×7c | i64.add | součet dvou celých 64bitových hodnot |
| 0×7d | i64.sub | rozdíl dvou celých 64bitových hodnot |
| 0×7e | i64.mul | součin dvou celých 64bitových hodnot |
| 0×7f | i64.div_s | podíl dvou celých 64bitových hodnot se znaménkem |
| 0×80 | i64.div_u | podíl dvou celých 64bitových hodnot bez znaménka |
| 0×81 | i64.rem_s | zbytek po dělení dvou celých 64bitových hodnot se znaménkem |
| 0×82 | i64.rem_u | zbytek po dělení dvou celých 64bitových hodnot bez znaménka |
| 0×83 | ||
| 0×84 | ||
| 0×85 | ||
| 0×86 | ||
| 0×87 | ||
| 0×88 | ||
| 0×89 | ||
| 0×8a | ||
| 0×8b | f32.abs | absolutní hodnota typu single/float |
| 0×8c | f32.neg | otočení znaménka u hodnoty typu single/float |
| 0×8d | ||
| 0×8e | ||
| 0×8f | ||
| 0×90 | ||
| 0×91 | f32.sqrt | druhá odmocnina z hodnoty typu single/float |
| 0×92 | f32.add | součet dvou hodnot typu single/float |
| 0×93 | f32.sub | rozdíl dvou hodnot typu single/float |
| 0×94 | f32.mul | součin dvou hodnot typu single/float |
| 0×95 | f32.div | podíl dvou hodnot typu single/float |
| 0×96 | f32.min | součet dvou hodnot typu single/float |
| 0×97 | f32.max | součet dvou hodnot typu single/float |
| 0×98 | ||
| 0×99 | f64.abs | absolutní hodnota typu double |
| 0×9a | f64.neg | otočení znaménka u hodnoty typu double |
| 0×9b | ||
| 0×9c | ||
| 0×9d | ||
| 0×9e | ||
| 0×9f | f64.sqrt | druhá odmocnina z hodnoty typu double |
| 0×a0 | f64.add | součet dvou hodnot typu double |
| 0×a1 | f64.sub | rozdíl dvou hodnot typu double |
| 0×a2 | f64.mul | součin dvou hodnot typu double |
| 0×a3 | f64.div | podíl dvou hodnot typu double |
| 0×a4 | f64.min | součet dvou hodnot typu double |
| 0×a5 | f64.max | součet dvou hodnot typu double |
| 0×a6 | ||
| 0×a7 | i32.wrap_i64 | de facto opak instrukce extend, převod hodnoty se ztrátou informace 64 na 32 bitů |
| 0×a8 | i32.trunc_f32_s | převod hodnoty typu float na celé číslo se znaménkem |
| 0×a9 | i32.trunc_f32_u | převod hodnoty typu float na celé číslo bez znaménka |
| 0×aa | i32.trunc_f64_s | převod hodnoty typu double na celé číslo se znaménkem |
| 0×ab | i32.trunc_f64_u | převod hodnoty typu double na celé číslo bez znaménka |
| 0×ac | i64.extend_i32_s | znaménkové rozšíření hodnoty (32 na 64 bitů) |
| 0×ad | i64.extend_i32_u | bezznaménkové rozšíření hodnoty (32 na 64 bitů) |
| 0×ae | i64.trunc_f32_s | převod hodnoty typu float na celé číslo se znaménkem |
| 0×af | i64.trunc_f32_u | převod hodnoty typu float na celé číslo bez znaménka |
| 0×b0 | i64.trunc_f64_s | převod hodnoty typu double na celé číslo se znaménkem |
| 0×b1 | i64.trunc_f64_u | převod hodnoty typu double na celé číslo bez znaménka |
| 0×b2 | f32.convert_i32_s | konverze celého čísla se znaménkem (32 bitů) na typ float |
| 0×b3 | f32.convert_i32_u | konverze celého čísla bez znaménka (32 bitů) na typ float |
| 0×b4 | f32.convert_i64_s | konverze celého čísla se znaménkem (32 bitů) na typ float |
| 0×b5 | f32.convert_i64_u | konverze celého čísla bez znaménka (32 bitů) na typ float |
| 0×b6 | f32.demote_f64 | převod hodnoty typu double na typ float |
| 0×b7 | f64.convert_i32_s | konverze celého čísla se znaménkem (64 bitů) na typ double |
| 0×b8 | f64.convert_i32_u | konverze celého čísla bez znaménka (64 bitů) na typ double |
| 0×b9 | f64.convert_i64_s | konverze celého čísla se znaménkem (64 bitů) na typ double |
| 0×ba | f64.convert_i64_u | konverze celého čísla bez znaménka (64 bitů) na typ double |
| 0×bb | f64.promote_f32 | převod hodnoty typu float na typ double |
| 0×bc | i32.reinterpret_f32 | pouze změna typu, nezmění se však jednotlivé bity slova |
| 0×bd | i64.reinterpret_f64 | pouze změna typu, nezmění se však jednotlivé bity slova |
| 0×be | f32.reinterpret_i32 | pouze změna typu, nezmění se však jednotlivé bity slova |
| 0×bf | f64.reinterpret_i64 | pouze změna typu, nezmění se však jednotlivé bity slova |
| 0×c0 | i32.extend8_s | znaménkové rozšíření hodnoty z 8 bitů na 32 bitů |
| 0×c1 | i32.extend16_s | znaménkové rozšíření hodnoty ze 16 bitů na 32 bitů |
| 0×c2 | i64.extend8_s | znaménkové rozšíření hodnoty z 8 bitů na 64 bitů |
| 0×c3 | i64.extend16_s | znaménkové rozšíření hodnoty ze 16 bitů na 64 bitů |
| 0×c4 | i64.extend32_s | znaménkové rozšíření hodnoty z 8 bitů na 64 bitů |
| 0×c5 | ||
| 0×c6 | ||
| 0×c7 | ||
| 0×c8 | ||
| 0×c9 | ||
| 0×ca | ||
| 0×cb | ||
| 0×cc | ||
| 0×cd | ||
| 0×ce | ||
| 0×cf | ||
| 0×d0 | ||
| 0×d1 | ||
| 0×d2 | ||
| 0×d3 | ||
| 0×d4 | ||
| 0×d5 | ||
| 0×d6 | ||
| 0×d7 | ||
| 0×d8 | ||
| 0×d9 | ||
| 0×da | ||
| 0×db | ||
| 0×dc | ||
| 0×dd | ||
| 0×de | ||
| 0×df | ||
| 0×e0 | ||
| 0×e1 | ||
| 0×e2 | ||
| 0×e3 | ||
| 0×e4 | ||
| 0×e5 | ||
| 0×e6 | ||
| 0×e7 | ||
| 0×e8 | ||
| 0×e9 | ||
| 0×ea | ||
| 0×eb | ||
| 0×ec | ||
| 0×ed | ||
| 0×ee | ||
| 0×ef | ||
| 0×f0 | ||
| 0×f1 | ||
| 0×f2 | ||
| 0×f3 | ||
| 0×f4 | ||
| 0×f5 | ||
| 0×f6 | ||
| 0×f7 | ||
| 0×f8 | ||
| 0×f9 | ||
| 0×fa | ||
| 0×fb | ||
| 0×fc | ||
| fd 0f | i8×16.splat | skalár převedený na 16 prvků typu byte (16×8 bitů) |
| fd 10 | i16×8.splat | skalár převedený na 8 prvků typu word (8×16 bitů) |
| fd 11 | i32×4.splat | skalár převedený na 4 prvky typu double word (4×32 bitů) |
| fd 12 | i64×2.splat | skalár převedený na 2 prvky typu quad word (2×64 bitů) |
| fd 13 | f32×4.splat | skalár převedený na 4 prvky typu single (4×32 bitů) |
| fd 14 | f64×2.splat | skalár převedený na 2 prvky typu double (2×64 bitů) |
| fd 15 | i8×16.extract_lane_s | 16 prvků typu byte (16×8 bitů) |
| fd 16 | i8×16.extract_lane_u | 16 prvků typu byte (16×8 bitů) |
| fd 17 | i8×16.replace_lane | 16 prvků typu byte (16×8 bitů) |
| fd 18 | i16×8.extract_lane_s | 8 prvků typu word (8×16 bitů) |
| fd 19 | i16×8.extract_lane_u | 8 prvků typu word (8×16 bitů) |
| fd 1a | i16×8.replace_lane | 8 prvků typu word (8×16 bitů) |
| fd 1b | i32×4.extract_lane | 4 prvky typu double word (4×32 bitů) |
| fd 1c | i32×4.replace_lane | 4 prvky typu double word (4×32 bitů) |
| fd 1d | i64×2.extract_lane | 2 prvky typu quad word (2×64 bitů) |
| fd 1e | i64×2.replace_lane | 2 prvky typu quad word (2×64 bitů) |
| fd 1f | f32×4.extract_lane | 4 prvky typu single (4×32 bitů) |
| fd 20 | f32×4.replace_lane | 4 prvky typu single (4×32 bitů) |
| fd 21 | f64×2.extract_lane | 2 prvky typu double (2×64 bitů) |
| fd 22 | f64×2.replace_lane | 2 prvky typu double (2×64 bitů) |
| fd 23 | i8×16.eq | test na rovnost prvků; 16 prvků typu byte (16×8 bitů) |
| fd 24 | i8×16.ne | test na nerovnost prvků; 16 prvků typu byte (16×8 bitů) |
| fd 25 | i8×16.lt_s | relace „menší než“; 16 prvků typu signed char (16×8 bitů) |
| fd 26 | i8×16.lt_u | relace „menší než“; 16 prvků typu unsigned char (16×8 bitů) |
| fd 27 | i8×16.gt_s | relace „větší než“; 16 prvků typu signed char (16×8 bitů) |
| fd 28 | i8×16.gt_u | relace „větší než“; 16 prvků typu unsigned char (16×8 bitů) |
| fd 29 | i8×16.le_s | relace „menší nebo rovno“; 16 prvků typu signed char (16×8 bitů) |
| fd 2a | i8×16.le_u | relace „menší nebo rovno“; 16 prvků typu unsigned char (16×8 bitů) |
| fd 2b | i8×16.ge_s | relace „větší nebo rovno“; 16 prvků typu signed char (16×8 bitů) |
| fd 2c | i8×16.ge_u | relace „větší nebo rovno“; 16 prvků typu unsigned char (16×8 bitů) |
| fd 2d | i16×8.eq | test na rovnost prvků; 8 prvků typu word (8×16 bitů) |
| fd 2e | i16×8.ne | test na nerovnost prvků; 8 prvků typu word (8×16 bitů) |
| fd 2f | i16×8.lt_s | relace „menší než“; 8 prvků typu signed word (8×16 bitů) |
| fd 30 | i16×8.lt_u | relace „menší než“; 8 prvků typu usigned word (8×16 bitů) |
| fd 31 | i16×8.gt_s | relace „větší než“; 8 prvků typu signed word (8×16 bitů) |
| fd 32 | i16×8.gt_u | relace „větší než“; 8 prvků typu usigned word (8×16 bitů) |
| fd 33 | i16×8.le_s | relace „menší nebo rovno“; 8 prvků typu signed word (8×16 bitů) |
| fd 34 | i16×8.le_u | relace „menší nebo rovno“; 8 prvků typu usigned word (8×16 bitů) |
| fd 35 | i16×8.ge_s | relace „větší nebo rovno“; 8 prvků typu signed word (8×16 bitů) |
| fd 36 | i16×8.ge_u | relace „větší nebo rovno“; 8 prvků typu usigned word (8×16 bitů) |
| fd 37 | i32×4.eq | test na rovnost prvků; 4 prvky typu long (4×32 bitů) |
| fd 38 | i32×4.ne | test na nerovnost prvků; 4 prvky typu long (4×32 bitů) |
| fd 39 | i32×4.lt_s | relace „menší než“; 4 prvky typu signed long (4×32 bitů) |
| fd 3a | i32×4.lt_u | relace „menší než“; 4 prvky typu usigned long (4×32 bitů) |
| fd 3b | i32×4.gt_s | relace „větší než“; 4 prvky typu signed long (4×32 bitů) |
| fd 3c | i32×4.gt_u | relace „větší než“; 4 prvky typu usigned long (4×32 bitů) |
| fd 3d | i32×4.le_s | relace „menší nebo rovno“; 4 prvky typu signed long (4×32 bitů) |
| fd 3e | i32×4.le_u | relace „menší nebo rovno“; 4 prvky typu usigned long (4×32 bitů) |
| fd 3f | i32×4.ge_s | relace „větší nebo rovno“; 4 prvky typu signed long (4×32 bitů) |
| fd 40 | i32×4.ge_u | relace „větší nebo rovno“; 4 prvky typu usigned long (4×32 bitů) |
| fd 41 | f32×4.eq | test na rovnost prvků; 4 prvky typu float (4×32 bitů) |
| fd 42 | f32×4.ne | relace „větší nebo rovno“; 4 prvky typu float (4×32 bitů) |
| fd 43 | f32×4.lt | test na nerovnost prvků; 4 prvky typu float (4×32 bitů) |
| fd 44 | f32×4.gt | relace „menší nebo rovno“; 4 prvky typu float (4×32 bitů) |
| fd 45 | f32×4.le | relace „menší než“; 4 prvky typu float (4×32 bitů) |
| fd 46 | f32×4.ge | relace „větší než“; 4 prvky typu float (4×32 bitů) |
| fd 47 | f64×2.eq | test na rovnost prvků; 2 prvky typu double (2×64 bitů) |
| fd 48 | f64×2.ne | relace „větší nebo rovno“; 2 prvky typu double (2×64 bitů) |
| fd 49 | f64×2.lt | test na nerovnost prvků; 2 prvky typu double (2×64 bitů) |
| fd 4a | f64×2.gt | relace „menší nebo rovno“; 2 prvky typu double (2×64 bitů) |
| fd 4b | f64×2.le | relace „menší než“; 2 prvky typu double (2×64 bitů) |
| fd 4c | f64×2.ge | relace „větší než“; 2 prvky typu double (2×64 bitů) |
| fd 61 | i8×16.neg | otočení znaménka vektorů se šestnácti prvky typu byte (16×8 bitů) |
| fd 6e | i8×16.add | součet vektorů se šestnácti prvky typu byte (16×8 bitů) |
| fd 71 | i8×16.sub | rozdíl vektorů se šestnácti prvky typu byte (16×8 bitů) |
| fd 81 01 | i16×8.neg | otočení znaménka vektorů s osmi typu word (8×16 bitů) |
| fd 8e 01 | i16×8.add | součet vektorů s osmi prvky typu word (8×16 bitů) |
| fd 91 01 | i16×8.sub | rozdíl vektorů s osmi prvky typu word (8×16 bitů) |
| fd 95 01 | i16×8.mul | součin odpovídajících si prvků vektorů typu word (8×16 bitů) |
| fd a1 01 | i32×4.neg | otočení znaménka vektorů se čtyřmi prvky typu double word (4×32 bitů) |
| fd ae 01 | i32×4.add | součet vektorů se čtyřmi prvky typu double word (4×32 bitů) |
| fd b1 01 | i32×4.sub | rozdíl vektorů se čtyřmi prvky typu double word (4×32 bitů) |
| fd b5 01 | i32×4.mul | součin odpovídajících si prvků vektorů typu double word (4×32 bitů) |
| fd c1 01 | i64×2.neg | otočení znaménka vektorů se dvěma prvky typu quat word (2×64 bitů) |
| fd ce 01 | i64×2.add | součet vektorů se dvěma prvky typu quad word (2×64 bitů) |
| fd d1 01 | i64×2.sub | rozdíl vektorů se dvěma prvky typu quat word (2×64 bitů) |
| fd d5 01 | i64×2.mul | součin odpovídajících si prvků vektorů typu quad word (2×64 bitů) |
| fd d6 01 | i64×2.eq | test na rovnost prvků; 2 prvky typu long long (2×64 bitů) |
| fd d7 01 | i64×2.ne | test na nerovnost prvků; 2 prvky typu long long (2×64 bitů) |
| fd d8 01 | i64×2.lt_s | relace „menší než“; 2 prvky typu long signed long (2×64 bitů) |
| fd d9 01 | i64×2.gt_s | relace „větší než“; 2 prvky typu long signed long (2×64 bitů) |
| fd da 01 | i64×2.le_s | relace „menší nebo rovno“; 2 prvky typu long signed long (2×64 bitů) |
| fd db 01 | i64×2.ge_s | relace „větší nebo rovno“; 2 prvky typu long signed long (2×64 bitů) |
| fd e1 01 | f32×4.neg | otočení znaménka vektorů se čtyřmi prvky tyypu single (4×32 bitů) |
| fd e3 01 | f32×4.sqrt | výpočet druhé odmocniny čtyř prvků typu single (4×32 bitů) |
| fd e6 01 | f32×4.mul | součin odpovídajících si prvků vektorů typu single (4×32 bitů) |
| fd e7 01 | f32×4.div | podíl odpovídajících si prvků vektorů typu single (4×32 bitů) |
| fd e4 01 | f32×4.add | součet vektorů se čtyřmi prvky typu single (4×32 bitů) |
| fd e5 01 | f32×4.sub | rozdíl vektorů se čtyřmi prvky typu single (4×32 bitů) |
| fd ed 01 | f64×2.neg | otočení znaménka vektorů se dvěma prvky typu double (2×64 bitů) |
| fd ef 01 | f64×2.sqrt | výpočet druhé odmocniny dvou prvků typu double (2×64 bitů) |
| fd f0 01 | f64×2.add | součet vektorů se dvěma prvky typu double (2×64 bitů) |
| fd f1 01 | f64×2.sub | rozdíl vektorů se dvěma prvky typu double (2×64 bitů) |
| fd f2 01 | f64×2.mul | součin odpovídajících prvků vektorů double (2×64 bitů) |
| fd f3 01 | f64×2.div | podíl odpovídajících prvků vektorů double (2×64 bitů) |
| 0×fe | ||
| 0×ff |
19. Články o SIMD, které doposud na Rootu vyšly
Podporou SIMD instrukcí na úrovni takzvaných intrinsic v jazyku C jsme se už na Rootu zabývali, stejně jako samotnými SIMD instrukcemi na úrovni assembleru (i když jen pro platformu x86 či x86–64 a nikoli pro WebAssembly). Pro úplnost jsou v této příloze uvedeny odkazy na příslušné články:
- Užitečné rozšíření GCC: podpora SIMD (vektorových) instrukcí
https://www.root.cz/clanky/uzitecne-rozsireni-gcc-podpora-simd-vektorovych-instrukci/ - Užitečné rozšíření GCC – podpora SIMD (vektorových) instrukcí: nedostatky technologie
https://www.root.cz/clanky/uzitecne-rozsireni-gcc-podpora-simd-vektorovych-instrukci-nedostatky-technologie/ - Podpora SIMD (vektorových) instrukcí na RISCových procesorech
https://www.root.cz/clanky/podpora-simd-vektorovych-instrukci-na-riscovych-procesorech/ - Podpora SIMD operací v GCC s využitím intrinsic pro nízkoúrovňové optimalizace
https://www.root.cz/clanky/podpora-simd-operaci-v-gcc-s-vyuzitim-intrinsic-pro-nizkourovnove-optimalizace/ - Podpora SIMD operací v GCC s využitím intrinsic: technologie SSE
https://www.root.cz/clanky/podpora-simd-operaci-v-gcc-s-vyuzitim-intrinsic-technologie-sse/ - Rozšíření instrukční sady „Advanced Vector Extensions“ na platformě x86–64
https://www.root.cz/clanky/rozsireni-instrukcni-sady-advanced-vector-extensions-na-platforme-x86–64/ - Rozšíření instrukční sady F16C, FMA a AVX-512 na platformě x86–64
https://www.root.cz/clanky/rozsireni-instrukcni-sady-f16c-fma-a-avx-512-na-platforme-x86–64/ - Rozšíření instrukční sady AVX-512 na platformě x86–64 (dokončení)
https://www.root.cz/clanky/rozsireni-instrukcni-sady-avx-512-na-platforme-x86–64-dokonceni/ - SIMD instrukce na platformě 80×86: instrukční sada MMX
https://www.root.cz/clanky/simd-instrukce-na-platforme-80×86-instrukcni-sada-mmx/ - SIMD instrukce na 80×86: dokončení popisu MMX, instrukce 3DNow!
https://www.root.cz/clanky/simd-instrukce-na-80–86-dokonceni-popisu-mmx-instrukce-3dnow/ - SIMD instrukce v rozšíření SSE
https://www.root.cz/clanky/simd-instrukce-v-rozsireni-sse/ - SIMD instrukce v rozšíření SSE (2. část)
https://www.root.cz/clanky/simd-instrukce-v-rozsireni-sse-2-cast/ - Pokročilejší SSE operace: přeskupení, promíchání a rozbalování prvků vektorů
https://www.root.cz/clanky/pokrocilejsi-sse-operace-preskupeni-promichani-a-rozbalovani-prvku-vektoru/ - Od instrukční sady SSE k sadě SSE2
https://www.root.cz/clanky/od-instrukcni-sady-sse-k-sade-sse2/ - Instrukční sady SIMD a automatické vektorizace prováděné překladačem GCC
https://www.root.cz/clanky/instrukcni-sady-simd-a-automaticke-vektorizace-provadene-prekladacem-gcc/ - Instrukční sady SIMD a automatické vektorizace prováděné překladačem GCC (2)
https://www.root.cz/clanky/instrukcni-sady-simd-a-automaticke-vektorizace-provadene-prekladacem-gcc-2/ - Pohled pod kapotu formátu WebAssembly: SIMD (vektorové) operace
https://www.root.cz/clanky/pohled-pod-kapotu-formatu-webassembly-simd-vektorove-operace/
20. Odkazy na Internetu
- Compiling C to WebAssembly without Emscripten
https://surma.dev/things/c-to-webassembly/ - Web Assemply: Text Format
https://webassembly.github.io/spec/core/text/index.html - WebAssembly: Binary Format
https://webassembly.github.io/spec/core/binary/index.html - WebAssembly
https://webassembly.org/ - WebAssembly na Wiki Golangu
https://github.com/golang/go/wiki/WebAssembly - The future of WebAssembly – A look at upcoming features and proposals
https://blog.scottlogic.com/2018/07/20/wasm-future.html - WebAssembly Design
https://github.com/WebAssembly/design - Využití WebAssembly z programovacího jazyka Go
https://www.root.cz/clanky/vyuziti-webassembly-z-programovaciho-jazyka-go/ - WebAssembly slibuje podstatné zrychlení webů, konec JavaScriptu se ale nekoná
https://www.lupa.cz/clanky/webassembly-slibuje-podstatne-zrychleni-webu-konec-javascriptu-se-ale-nekona/ - List of languages that compile to JS
https://github.com/jashkenas/coffeescript/wiki/List-of-languages-that-compile-to-JS - asm.js
http://asmjs.org/ - Top 23 WASM Open-Source Projects
https://www.libhunt.com/topic/wasm - Made with WebAssembly
https://madewithwebassembly.com/ - The Top 1,790 Wasm Open Source Projects on Github
https://awesomeopensource.com/projects/wasm - Sanspiel
https://sandspiel.club/ - Painting on HTML5 Canvas with Rust WASM
https://www.subarctic.org/painting_on_html5_canvas_with_rust_wasm.html - Writing WebAssembly By Hand
https://blog.scottlogic.com/2018/04/26/webassembly-by-hand.html - WebAssembly Specification
https://webassembly.github.io/spec/core/index.html - Index of Instructions
https://webassembly.github.io/spec/core/appendix/index-instructions.html - The WebAssembly Binary Toolkit
https://github.com/WebAssembly/wabt - Will WebAssembly replace JavaScript? Or Will WASM Make JavaScript More Valuable in Future?
https://dev.to/vaibhavshah/will-webassembly-replace-javascript-or-will-wasm-make-javascript-more-valuable-in-future-5c6e - Webassembly as 32bit and 64bit
https://stackoverflow.com/questions/78580226/webassembly-as-32bit-and-64bit - Portability
https://webassembly.org/docs/portability/ - Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
https://www.root.cz/clanky/hexadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/ - Nástroj objdump: švýcarský nožík pro vývojáře
https://www.root.cz/clanky/nastroj-objdump-svycarsky-nozik-pro-vyvojare/ - Getting Started: Building and Running Clang
https://clang.llvm.org/get_started.html - Clang: a C language family frontend for LLVM
https://clang.llvm.org/ - Scheduling LLVM Passes with the New Pass Manager
https://stephenverderame.github.io/blog/scheduling_llvm/ - C data types
https://en.wikipedia.org/wiki/C_data_types - WebAssembly data types
https://webassembly.github.io/spec/core/syntax/types.html - WebAssembly Opcodes
https://pengowray.github.io/wasm-ops/ - Advanced tools (for WebAssembly)
https://webassembly.org/getting-started/advanced-tools/ - Binaryen
https://github.com/WebAssembly/binaryen - Using SIMD with WebAssembly
https://emscripten.org/docs/porting/simd.html - GCC documentation: Extensions to the C Language Family
https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html#C-Extensions - GCC documentation: Using Vector Instructions through Built-in Functions
https://gcc.gnu.org/onlinedocs/gcc/Vector-Extensions.html - SSE (Streaming SIMD Extentions)
http://www.songho.ca/misc/sse/sse.html - Timothy A. Chagnon: SSE and SSE2
http://www.cs.drexel.edu/~tc365/mpi-wht/sse.pdf - Intel corporation: Extending the Worldr's Most Popular Processor Architecture
http://download.intel.com/technology/architecture/new-instructions-paper.pdf - SIMD architectures:
http://arstechnica.com/old/content/2000/03/simd.ars/ - 3Dnow! Technology Manual
AMD Inc., 2000 - Intel MMXTM Technology Overview
Intel corporation, 1996 - MultiMedia eXtensions
http://softpixel.com/~cwright/programming/simd/mmx.phpi - AMD K5 („K5“ / „5k86“)
http://www.pcguide.com/ref/cpu/fam/g5K5-c.html - Arm Helium
https://www.arm.com/technologies/helium - SIMD proposal for WebAssembly
https://github.com/webassembly/simd/ - Single instruction, multiple data
https://en.wikipedia.org/wiki/Single_instruction%2C_multiple_data - Parallel computing
https://en.wikipedia.org/wiki/Parallel_computing - Flynn's taxonomy
https://en.wikipedia.org/wiki/Flynn%27s_taxonomy - WebAssembly opcode table
https://pengowray.github.io/wasm-ops/

