Obsah
1. WebAssembly a vektorové operace
2. Podpora SIMD operací na historických i současných architekturách mikroprocesorů
3. Podpora SIMD operací ve WebAssembly
6. Jakým způsobem lze SIMD využít pro programy psané v jazyku C?
7. Definice vektorů podporované překladači jazyka C
8. Vektory shodné bitové šířky, ovšem obsahující prvky odlišných typů
9. Operace prováděné s celými vektory
10. Překlad funkce pro součet dvou vektorů do WebAssembly
11. Překlad do WebAssembly s explicitním povolením vektorových instrukcí
12. Součet vektorů s dvojnásobným počtem prvků
13. Součet vektorů s celočíselnými prvky různých typů
14. Součet vektorů s prvky s plovoucí řádovou čárkou
15. Všechny instrukce pro součet vektorů
16. Další celočíselné operace prováděné s prvky vektorů
18. Tabulka se všemi doposud popsanými instrukcemi
19. Články o SIMD, které doposud na Rootu vyšly
1. WebAssembly a vektorové operace
Do specifikace WebAssembly byly k základním instrukcím později přidány i další rozšiřující instrukce a dokonce celé sady nových instrukcí. Mezi tyto instrukce patří zejména instrukce umožňující provádět vektorové operace (resp. přesněji řečeno SIMD operace). Překladače LLVM tyto instrukce podporují a navíc pro ně máme přímou podporu i přímo v Clangu (a to dokonce na několika úrovních). A právě s tímto (ve světě videí a umělé inteligence) velmi důležitým rozšířením WebAssembly se budeme zabývat v dnešním článku.
„…sequential computers are approaching a fundamental physical limit on their potential power. Such a limit is the speed of light…“
Budeme se tedy zabývat popisem způsobu implementace operací typu SIMD (Single Instruction, Multiple Data) ve WebAssembly. Jedná se o velmi důležitou technologii protože SIMD neboli (poněkud nepřesně řečeno) vektorové instrukce v současnosti patří ke standardní výbavě prakticky všech variant moderních mikroprocesorů. Připomeňme si, že z hlediska dosahovaného výpočetního výkonu leží na samém „výkonnostním dně“ klasické skalární mikroprocesory s architekturou CISC, které vykonávají všechny instrukce postupně a dokončení jedné instrukce může v závislosti na jejich složitosti trvat i několik desítek strojových taktů.
Předností těchto procesorů (v případě WebAssembly pak virtuálních strojů) může být poměrně velká informační hustota instrukční sady (například i díky tomu, že operandy některých instrukcí jsou zadány implicitně popř. jsou podporovány instrukce proměnné délky), což mj. znamená, že se procesory tohoto typu po poměrně dlouhou dobu obešly bez nutnosti využití drahých vyrovnávacích pamětí první a druhé úrovně (L1 cache, L2 cache). Klasické procesory s architekturou CISC byly založeny na mikroprogramovém řadiči vybaveném pamětí mikroinstrukcí a teprve později začaly být tyto procesory doplňovány technologiemi získanými z jiných architektur – instrukční pipeline, prediktorem skoků, vektorovými instrukcemi (což byly oblasti klasických RISCových architektur) atd. Moderní mikroprocesory se tak od klasických CISC prakticky ve všech technologických detailech odlišují.
Výpočetní výkon mikroprocesorů se podařilo poměrně výrazným způsobem zvýšit u konkurenční architektury RISC zavedením takzvané instrukční pipeline. Provedení jedné instrukce sice stále trvalo větší počet strojových cyklů, ovšem díky rozfázování operací v instrukční pipeline bylo umožněno překrývání většího množství instrukcí, a to bez nutnosti zavádění skutečné paralelizace (která vede k velkému nárůstu složitosti a tím i ceny čipu). Spolu se zavedením mikroprocesorů RISC se skutečně stalo, že reálný i špičkový výpočetní výkon procesorů vzrostl, ale relativně brzy bylo nutné k těmto čipům přidat vyrovnávací paměti (cache), jelikož rychlost procesorů rostla mnohem rychleji, než vybavovací doba pamětí. Tento rozpor mezi rychlostmi obou nejdůležitějších součástí moderních počítačů ostatně trvá dodnes.
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.
Pro další zvýšení výpočetního výkonu však bylo nutné použít další technologie, například instrukční sadu VLIW (Very Long Instruction Word), která však – opět – měla velké nároky na rychlost pamětí. Podobně jako u procesorů RISC, i u VLIW bylo pro zmírnění požadavků na rychlost pamětí možné použít Harvardskou architekturu, tj. oddělení paměti programu od paměti dat (programová paměť navíc mohla mít větší šířku datové sběrnice odpovídající šířce instrukčních slov). Kombinace VLIW a Harvardské architektury byla používána například u digitálních signálových procesorů (DSP).
Další zvýšení výkonu umožňují právě vektorové instrukce (přesněji SIMD instrukce), které ale mají jeden poměrně zásadní nedostatek – sémantickou mezeru mezi imperativním „skalárním“ kódem psaným například v jazyku C (C++ atd.) a instrukční sadou, která je SIMD (jinými slovy – běžné programovací jazyky neumožňují dostatečně popsat vektorové operace). Totéž ostatně platí i pro WebAssembly, takže si dnes ukážeme, jakým způsobem je tato problematika řešena a jak lze vektorové instrukce ve WebAssembly využít.
2. Podpora SIMD operací na historických i současných architekturách mikroprocesorů
SIMD operace nalezneme v mnoha historických architekturách mikroprocesorů, ale (což je důležitější) i v prakticky všech moderních mikroprocesorech. V této kapitole se o nejvýznamnějších historických i současných technologiích zmíníme.
Nejznámější implementace instrukcí typu SIMD na mikroprocesorech s architekturou RISC, ať již se jedná o instrukce určené pro operace s celými čísly či s čísly reálnými (přesněji řečeno s plovoucí řádovou čárkou), jsou vypsány v následující tabulce:
| # | Zkratka/název | Plný název | Rodina procesorů |
|---|---|---|---|
| 1 | MAX-1 | Multimedia Acceleration eXtensions v1 | HP-PA RISC |
| 2 | MAX-2 | Multimedia Acceleration eXtensions v2 | HP-PA RISC |
| 3 | VIS 1 | Visual Instruction v1 | Set SPARC V9 |
| 4 | VIS 2 | Visual Instruction v2 | Set SPARC V9 |
| 5 | AltiVec | (obchodní názvy Velocity Engine, VMX) | PowerPC |
| 6 | MDMX | MIPS Digital Media eXtension (MaDMaX) | MIPS |
| 7 | MIPS-3D | MIPS-3D | MIPS |
| 8 | MVI | Motion Video Instructions | DEC Alpha |
| 9 | NEON | Advanced SIMD | Cortex (ARMv7, ARMv8) |
| 10 | Packed SIMD | Packed SIMD | RISC-V |
| 11 | Vector Set | Vector Set | RISC-V |
| 12 | Scalable Vector Extension (SVE) | ARMv8.2-A a novější | |
| 13 | VFP | Vector Floating Point | původně nikoli SIMD, ale pouze sekvenční operace nad vektory |
| 14 | Helium | MVE pro čipy Cortex-M | vybraná jádra Cortex-M |
Zkusme se nyní podívat na seznam různých SIMD (neboli nesprávně řečeno „vektorových“) rozšíření původní instrukční sady x86:
| Technologie | Rok uvedení | Společnost | Poprvé použito v čipu |
|---|---|---|---|
| MMX | 1996 | Intel | Intel Pentium P5 |
| 3DNow! | 1998 | AMD | AMD K6–2 |
| SSE | 1999 | Intel | Intel Pentium III (mikroarchitektura P6) |
| SSE2 | 2001 | Intel | Intel Pentium 4 (mikroarchitektura NetBurst) |
| SSE3 | 2004 | Intel | Intel Pentium 4 (Prescott) |
| SSSE3 | 2006 | Intel | mikroarchitektura Intel Core |
| SSE4 | 2006 | Intel+AMD | AMD K10 (SSE4a) , mikroarchitektura Intel Core |
| SSE5 | 2007 | AMD | (nakonec rozděleno do menších celků), mikroarchitektura Bulldozer |
| AVX | 2008 | Intel | mikroarchitektura Sandy Bridge |
| F16C (CVT16) | 2009 | AMD | Jaguar, Puma, Bulldozer atd. |
| XOP | 2009 | AMD | mikroarchitektura Bulldozer |
| FMA3 | 2012 | AMD | mikroarchitektura Piledriver, Intel: Haswell a Broadwell |
| FMA4 | 2011 | AMD | mikroarchitektura Bulldozer (pozdější architektury po Zen 1 již ne) |
| AVX2 | 2013 | Intel | mikroarchitektura Haswell |
| AVX-512 | 2013 | Intel | Knights Landing |
| AMX | 2020 | Intel | Sapphire Rapids |
Pro někoho může být taktéž zajímavá i informace o tom, jak velké změny v instrukční sadě mikroprocesorů byly vlastně při přidávání nových „vektorových“ rozšiřujících instrukčních sad typu SIMD provedeny. To nám ukáže další tabulka. Je pouze nutné dát si pozor na to, že počty nových instrukcí zavedených v rámci těchto nových technologií, které jsou vypsány v tabulce pod odstavcem, nemusí přesně souhlasit s počty uváděnými v jiných informačních materiálech. Je tomu tak především z toho důvodu, že se v některých případech rozlišuje i datový typ, s nímž instrukce pracují (například se může jednat o součet vektoru s 32 bitovými hodnotami nebo 64bitovými hodnotami reprezentovanými v obou případech ve formátu s plovoucí řádovou čárkou) a někdy se taková instrukce do celkové sumy započítává pouze jedenkrát. Nicméně údaje vypsané v níže uvedené tabulce by měly být konzistentní, protože se jedná o počty nově přidaných operačních kódů instrukcí (například u dále zmíněné instrukční sady SSE2 končí instrukce znakem D, S, I či Q podle typu zpracovávaných dat/operandů):
| Název technologie | Počet nových instrukcí |
|---|---|
| MMX | 56 |
| 3DNow! | 21 |
| SSE | 70 |
| SSE2 | 144 |
| SSE3 | 13 |
| SSSE3 | 32 (ve skutečnosti vlastně jen 16 instrukcí, ovšem pro dva datové typy) |
| SSE4 | 54 (z toho 47 v rámci SSE4.1, zbytek v rámci SSE4.2) |
| SSE5 | 170 (z toho 46 základních instrukcí) |
| F16C | 4 |
3. Podpora SIMD operací ve WebAssembly
Nyní se konečně dostáváme k WebAssembly. Původní instrukční sada WebAssembly byla čistě „skalární“, což znamená, že veškeré operace (výpočty, podmínky pro smyčky a rozvětvení atd.) probíhaly se skalárními hodnotami. Ovšem v době uvádění WebAssembly se na mainstreamových platformách mikroprocesorů začaly poměrně rychlým způsobem rozvíjet instrukční sady se SIMD operacemi. To, společně s tím, že bylo nutné podporovat například přehrávání videí a popř. i celé SW kodeky, vedlo k tomu, že do WebAssembly byla přidána podpora pro operace s vektory hodnot. Tyto vektory mají vždy šířku 128 bitů (minimálně v současné verzi WebAssembly), přičemž těchto 128 bitů může být rozděleno do různých typů prvků. Typicky se pracuje s vektory obsahujícími čtyři hodnoty typu single/float, ale může se jednat i o osmici celých čísel atd.
Toto vektorové rozšíření WebAssembly je v současnosti široce podporováno:
| Prohlížeč | Verze | Vydáno |
|---|---|---|
| Chrome | ≥ 91 | Květen 2021 |
| Firefox | ≥ 89 | Červen 2021 |
| Safari | ≥ 16.4 | Březen 2023 |
| Node.js | ≥ 16.4 | Červen 2021 |
4. Typ v128
O datových typech, které jsou podporovány běžnými skalárními instrukcemi WebAssembly, jsme se již zmínili v úvodním článku. Připomeňme si, že se jedná o typy se jmény i32, i64, f32 a f64. V rámci rozšíření WebAssembly o SIMD operace byla přidána podpora pro další datový typ nazvaný v128. Název tohoto typu prozrazuje jeho základní vlastnosti – jedná se o vektor, který je uložen ve 128 bitech. Ovšem jaké prvky budou ve vektoru uloženy závisí na konkrétní instrukci – může se jednat o celá čísla se znaménkem nebo bez znaménka, hodnoty s plovoucí řádovou čárkou s jednoduchou nebo dvojitou přesností nebo dokonce o jedinou 128bitovou hodnotu (ostatně i některé skalární instrukce mají různé varianty, podle použitých typů operandů).
V jazyce C je tento typ definován následujícím způsobem:
typedef int32_t v128_t __attribute__((__vector_size__(16), __aligned__(16)));
S tím, jakým způsobem se 128bitový vektor „rozpadá“ do jednotlivých prvků vektorů (různého typu) se podrobněji seznámíme v praktické části dnešního článku. V Clangu ovšem můžeme v případě potřeby využít následující definice (ovšem podtržítka naznačují, že se jedná o interní identifikátory):
typedef int32_t __v128_u __attribute__((__vector_size__(16), __aligned__(1))); typedef signed char __i8x16 __attribute__((__vector_size__(16), __aligned__(16))); typedef unsigned char __u8x16 __attribute__((__vector_size__(16), __aligned__(16))); typedef short __i16x8 __attribute__((__vector_size__(16), __aligned__(16))); typedef unsigned short __u16x8 __attribute__((__vector_size__(16), __aligned__(16))); typedef int __i32x4 __attribute__((__vector_size__(16), __aligned__(16))); typedef unsigned int __u32x4 __attribute__((__vector_size__(16), __aligned__(16))); typedef long long __i64x2 __attribute__((__vector_size__(16), __aligned__(16))); typedef unsigned long long __u64x2 __attribute__((__vector_size__(16), __aligned__(16))); typedef float __f32x4 __attribute__((__vector_size__(16), __aligned__(16))); typedef double __f64x2 __attribute__((__vector_size__(16), __aligned__(16))); typedef __fp16 __f16x8 __attribute__((__vector_size__(16), __aligned__(16))); typedef signed char __i8x8 __attribute__((__vector_size__(8), __aligned__(8))); typedef unsigned char __u8x8 __attribute__((__vector_size__(8), __aligned__(8))); typedef short __i16x4 __attribute__((__vector_size__(8), __aligned__(8))); typedef unsigned short __u16x4 __attribute__((__vector_size__(8), __aligned__(8))); typedef int __i32x2 __attribute__((__vector_size__(8), __aligned__(8))); typedef unsigned int __u32x2 __attribute__((__vector_size__(8), __aligned__(8))); typedef float __f32x2 __attribute__((__vector_size__(8), __aligned__(8)));
5. Instrukční prefix 0×FD
Všechny doposud popsané instrukce WebAssembly používaly jediný bajt pro uložení operačního kódu instrukce a většinou i pro určení, s jakými operandy se má instrukce provést (připomeňme si například několik variant instrukce pro součet dvou skalárních hodnot). Teoreticky tedy byla instrukční sada WebAssembly omezena na 256 instrukcí. U SIMD instrukcí je tomu jinak, protože těchto instrukcí existuje takové množství, že bylo nutné je všechny sjednotit do jediné skupiny. Tato skupina začíná prefixem 0×FD, za kterým následuje operační kód instrukce – SIMD instrukcí tedy může být definováno až 256 – a nutno říci, že prakticky všechny dostupné operační kódy z této skupiny byly skutečně využity.
6. Jakým způsobem lze SIMD využít pro programy psané v jazyku C?
SIMD operace (resp. přesněji řečeno instrukce) podporované ve WebAssembly je v praxi možné generovat různými způsoby, které se od sebe odlišují mírou abstrakce (a tedy vlastně i pracností):
- Povolením automatické vektorizace kódu, což kombinace frontend překladače Clang a backend překladače LLVM v současné verzi plně podporuje. Výhodou tohoto řešení je fakt, že není nutné modifikovat existující zdrojové kódy, nevýhodou pak stále ještě nedokonalosti překladačů v této oblasti (i když se situace stále zlepšuje).
- Taktéž je možné využít takzvané vektorové rozšíření Clangu, které se podobá vektorovému rozšíření GCC, jímž jsme se na stránkách Roota již zabývali (viz též odkazy na příslušné články uvedené na konci článku). Základní operace s vektory definovanými tímto způsobem jsou totiž prováděny právě SIMD instrukcemi definovanými ve WebAssembly.
- Další nabízenou možností je použití specializovaných intrinsic definujících SIMD operace podporované ve WebAssembly. Tyto intrinsic jsou definovány v hlavičkovém souboru wasm_simd128.h. Pozor: jedná se o odlišné intrinsic, než jaké jsme si popsali v souvislosti s GCC (viz další dva body).
- Alternativně je možné použít původní intrinsic pro rozšíření instrukční sady x86: x86 SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2, AVX, AVX2 nebo AVX-512 (popř. různé podsady instrukcí). Tyto intrinsic jsou definovány v hlavičkových souborech *mmintrin.h a ještě se k nim později vrátíme.
- Podobné intrinsic existují i pro architekturu ARM a rozšíření instrukční sady NEON. Tyto intrinsic jsou definovány v hlavičkovém souboru arm_neon.h.
7. Definice vektorů podporované překladači jazyka C
V oblasti SIMD vládne „instrukční babylon“, a to jak na architektuře x86, tak i u ARMu. Ani vývojářům a ani autorům překladačů to nijak neulehčuje život. Jedno z řešení tohoto stavu spočívá v tom, že překladače jazyka C podporují takzvané obecné vektorové operace. A jednou z realizací této myšlenky je rozšíření Clangu i GCC C (a C++) o de facto nové datové typy „vektor určité bajtové délky“ (dále se soustředíme pouze na Clang).
Podívejme se na následující demonstrační příklad, v němž je definován nový datový typ nazvaný i8×16 (jméno může být pochopitelně jakékoli). Jedná se o vektor o délce šestnácti bajtů, který obsahuje prvky typu signed char, což zde konkrétně může znamenat, že se do vektoru vejde celkem šestnáct těchto prvků za předpokladu, že sizeof(signed char)==1 (a to při překladu pro WebAssembly pochopitelně platí):
#include <stdio.h>
typedef signed char i8x16 __attribute__((vector_size(16)));
int main(void)
{
printf("scalar signed byte: %ld byte(s)\n", sizeof(signed char));
printf("vector signed bytes: %ld byte(s)\n", sizeof(i8x16));
return 0;
}
Výsledek, který získáme po překladu a spuštění tohoto demonstračního příkladu:
scalar signed byte: 1 byte(s) vector signed bytes: 16 byte(s)
8. Vektory shodné bitové šířky, ovšem obsahující prvky odlišných typů
Naprosto stejným způsobem si můžeme nadefinovat vektory o šířce šestnácti bajtů (tedy stále 128 bitů) s prvky různých typů:
#include <stdio.h>
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 int i64x2 __attribute__((vector_size(16)));
typedef unsigned long int u64x2 __attribute__((vector_size(16)));
int main(void)
{
printf("signed char: %ld bytes\n", sizeof(signed char));
printf("unsigned char: %ld bytes\n", sizeof(unsigned char));
printf("signed short: %ld bytes\n", sizeof(signed short int));
printf("unsigned short: %ld bytes\n", sizeof(unsigned short int));
printf("signed int: %ld bytes\n", sizeof(signed int));
printf("unsigned int: %ld bytes\n", sizeof(unsigned int));
printf("signed long: %ld bytes\n", sizeof(signed long int));
printf("unsigned long: %ld bytes\n", sizeof(unsigned long int));
printf("\n");
printf("vector signed char: %ld bytes\n", sizeof(i8x16));
printf("vector unsigned char: %ld bytes\n", sizeof(u8x16));
printf("vector signed short: %ld bytes\n", sizeof(i16x8));
printf("vector unsigned short: %ld bytes\n", sizeof(u16x8));
printf("vector signed int: %ld bytes\n", sizeof(i32x4));
printf("vector unsigned int: %ld bytes\n", sizeof(u32x4));
printf("vector signed long: %ld bytes\n", sizeof(i64x2));
printf("vector unsigned long: %ld bytes\n", sizeof(u64x2));
return 0;
}
Výsledky by měly vypadat následovně:
signed char: 1 bytes unsigned char: 1 bytes signed short: 2 bytes unsigned short: 2 bytes signed int: 4 bytes unsigned int: 4 bytes signed long: 8 bytes unsigned long: 8 bytes vector signed char: 16 bytes vector unsigned char: 16 bytes vector signed short: 16 bytes vector unsigned short: 16 bytes vector signed int: 16 bytes vector unsigned int: 16 bytes vector signed long: 16 bytes vector unsigned long: 16 bytes
A konečně – vyzkoušíme si definici vektorů o šířce 128bitů, které budou obsahovat čtyři prvky typu float (jednoduchá přesnost) nebo dva prvky typu double (dvojnásobná přesnost) popř. osm prvků typu fp16 (poloviční přesnost):
#include <stdio.h>
typedef float f32x4 __attribute__((vector_size(16)));
typedef double f64x2 __attribute__((vector_size(16)));
typedef __fp16 f16x8 __attribute__((vector_size(16)));
int main(void)
{
printf("float: %ld bytes\n", sizeof(float));
printf("double: %ld bytes\n", sizeof(double));
printf("fp16: %ld bytes\n", sizeof(__fp16));
printf("\n");
printf("vector float: %ld bytes\n", sizeof(f32x4));
printf("vector double: %ld bytes\n", sizeof(f64x2));
printf("vector fp16: %ld bytes\n", sizeof(f16x8));
return 0;
}
Opět se podívejme na zobrazené výsledky:
float: 4 bytes double: 8 bytes fp16: 2 bytes vector float: 16 bytes vector double: 16 bytes vector fp16: 16 bytes
9. Operace prováděné s celými vektory
S celými vektory (definovanými tak, jak jsme si ukázali v předchozích dvou kapitolách) lze pochopitelně provádět i základní aritmetické operace, což je ukázáno na dalším příkladu, společně s ukázkou toho, jak se vlastně vektory naplní daty. Budeme provádět součet vektorů obsahujících šestnáct prvků typu signed char:
#include <stdio.h>
typedef signed char i8x16 __attribute__((vector_size(16)));
i8x16 add(i8x16 x, i8x16 y) {
return x+y;
}
void print_vector(i8x16 *v) {
int i;
printf("[");
for (i=0; i<16; i++) {
printf(" %2d", (*v)[i]);
}
printf("]\n");
}
int main(void) {
i8x16 v1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
i8x16 v2 = {10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10};
i8x16 v3 = v1+v2;
print_vector(&v1);
print_vector(&v2);
print_vector(&v3);
return 0;
}
Po překladu a spuštění tohoto demonstračního příkladu (na běžné platformě x86 atd.) by se měly napřed zobrazit obsahy původních vektorů a následně i vektor, který vznikne jejich součtem:
[ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16] [ 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10] [ 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26]
Pro účely překladu do WebAssembly příklad zkrátíme do jediné funkce add, pochopitelně doplněné definicí typu vektorů, které se budou sčítat:
typedef signed char i8x16 __attribute__((vector_size(16)));
i8x16 add(i8x16 x, i8x16 y) {
return x+y;
}
10. Překlad funkce pro součet dvou vektorů do WebAssembly
Funkci add z předchozí kapitoly přeložíme standardním způsobem, který jsme až doposud (úspěšně) používali, tedy s využitím tohoto skriptu:
clang -Os --target=wasm32 -emit-llvm -c -S ${1}.c
llc -march=wasm32 -filetype=obj ${1}.ll -o ${1}.wasm
wasm-objdump -d ${1}.wasm
Výsledek může vypadat následovně (ovšem různé verze Clangu generují nepatrně odlišný kód):
simd_5.wasm: file format wasm 0x1 Code Disassembly: 000062 func[0] <add>: 000063: 20 00 | local.get 0 000065: 20 19 | local.get 25 000067: 20 09 | local.get 9 000069: 6a | i32.add 00006a: 3a 00 08 | i32.store8 0 8 00006d: 20 00 | local.get 0 00006f: 20 15 | local.get 21 000071: 20 05 | local.get 5 000073: 6a | i32.add 000074: 3a 00 04 | i32.store8 0 4 000077: 20 00 | local.get 0 000079: 20 13 | local.get 19 00007b: 20 03 | local.get 3 00007d: 6a | i32.add 00007e: 3a 00 02 | i32.store8 0 2 000081: 20 00 | local.get 0 000083: 20 12 | local.get 18 000085: 20 02 | local.get 2 000087: 6a | i32.add 000088: 3a 00 01 | i32.store8 0 1 00008b: 20 00 | local.get 0 00008d: 20 11 | local.get 17 00008f: 20 01 | local.get 1 000091: 6a | i32.add 000092: 3a 00 00 | i32.store8 0 0 000095: 20 00 | local.get 0 000097: 41 0f | i32.const 15 000099: 6a | i32.add 00009a: 20 20 | local.get 32 00009c: 20 10 | local.get 16 00009e: 6a | i32.add 00009f: 3a 00 00 | i32.store8 0 0 0000a2: 20 00 | local.get 0 0000a4: 41 0e | i32.const 14 0000a6: 6a | i32.add 0000a7: 20 1f | local.get 31 0000a9: 20 0f | local.get 15 0000ab: 6a | i32.add 0000ac: 3a 00 00 | i32.store8 0 0 0000af: 20 00 | local.get 0 0000b1: 41 0d | i32.const 13 0000b3: 6a | i32.add 0000b4: 20 1e | local.get 30 0000b6: 20 0e | local.get 14 0000b8: 6a | i32.add 0000b9: 3a 00 00 | i32.store8 0 0 0000bc: 20 00 | local.get 0 0000be: 41 0c | i32.const 12 0000c0: 6a | i32.add 0000c1: 20 1d | local.get 29 0000c3: 20 0d | local.get 13 0000c5: 6a | i32.add 0000c6: 3a 00 00 | i32.store8 0 0 0000c9: 20 00 | local.get 0 0000cb: 41 0b | i32.const 11 0000cd: 6a | i32.add 0000ce: 20 1c | local.get 28 0000d0: 20 0c | local.get 12 0000d2: 6a | i32.add 0000d3: 3a 00 00 | i32.store8 0 0 0000d6: 20 00 | local.get 0 0000d8: 41 0a | i32.const 10 0000da: 6a | i32.add 0000db: 20 1b | local.get 27 0000dd: 20 0b | local.get 11 0000df: 6a | i32.add 0000e0: 3a 00 00 | i32.store8 0 0 0000e3: 20 00 | local.get 0 0000e5: 41 09 | i32.const 9 0000e7: 6a | i32.add 0000e8: 20 1a | local.get 26 0000ea: 20 0a | local.get 10 0000ec: 6a | i32.add 0000ed: 3a 00 00 | i32.store8 0 0 0000f0: 20 00 | local.get 0 0000f2: 41 07 | i32.const 7 0000f4: 6a | i32.add 0000f5: 20 18 | local.get 24 0000f7: 20 08 | local.get 8 0000f9: 6a | i32.add 0000fa: 3a 00 00 | i32.store8 0 0 0000fd: 20 00 | local.get 0 0000ff: 41 06 | i32.const 6 000101: 6a | i32.add 000102: 20 17 | local.get 23
Povšimněte si, že se v tomto případě ve skutečnosti žádná vektorová operace neprovedla, protože zde můžeme vidět sérii „skalárních“ součtů:
000069: 6a | i32.add ... 000073: 6a | i32.add ... 00007d: 6a | i32.add ... 000087: 6a | i32.add ... 000091: 6a | i32.add ... 000099: 6a | i32.add ... 00009e: 6a | i32.add ... 0000a6: 6a | i32.add ... 0000ab: 6a | i32.add ... 0000b3: 6a | i32.add ... 0000b8: 6a | i32.add ... 0000c0: 6a | i32.add ... 0000c5: 6a | i32.add ... 0000cd: 6a | i32.add ... 0000d2: 6a | i32.add ... 0000da: 6a | i32.add ... 0000df: 6a | i32.add ... 0000e7: 6a | i32.add ... 0000ec: 6a | i32.add ... 0000f4: 6a | i32.add ... 0000f9: 6a | i32.add ... 000101: 6a | i32.add
11. Překlad do WebAssembly s explicitním povolením vektorových instrukcí
Aby se ve WebAssembly používaly vektorové instrukce, je nutné skript určený pro překlad nepatrným způsobem upravit. Budeme muset překladači Clang explicitně povolit generování SIMD instrukcí, a to přepínačem -msimd128. Upravený skript by tedy mohl vypadat následovně:
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
Nyní se funkce add skutečně přeloží do podoby, v níž je použita SIMD instrukce nazvaná i8×16.add, tedy „vektorová“ podoba skalární operace add:
simd_5.wasm: file format wasm 0x1 Code Disassembly: 000043 func[0] <add>: 000044: 20 01 | local.get 1 000046: 20 00 | local.get 0 000048: fd 6e | i8x16.add 00004a: 0b | end
Výsledek by tedy měl být při interpretaci nebo JITování cca 16× rychlejší!
12. Součet vektorů s dvojnásobným počtem prvků
WebAssembly sice omezuje vektorové operace na 128bitové vektory, ovšem to neznamená, že se musíme tímto omezením řídit i v céčku. Pokusme se nyní realizovat funkci pro součet vektorů, které budou obsahovat 32 prvků typu signed char. Bude se tedy jednat o 256bitové vektory:
typedef signed char i8x32 __attribute__((vector_size(32)));
i8x32 add(i8x32 x, i8x32 y) {
return x+y;
}
Z výsledku překladu do WebAssembly je patrné, že se vektory sečtou po částech (nyní zcela přesně po polovinách):
simd_6.wasm: file format wasm 0x1 Code Disassembly: 000045 func[0] <add>: 000046: 20 00 | local.get 0 000048: 20 04 | local.get 4 00004a: 20 02 | local.get 2 00004c: fd 6e | i8x16.add 00004e: fd 0b 04 10 | v128.store 4 16 000052: 20 00 | local.get 0 000054: 20 03 | local.get 3 000056: 20 01 | local.get 1 000058: fd 6e | i8x16.add 00005a: fd 0b 04 00 | v128.store 4 0 00005e: 0b | end
13. Součet vektorů s celočíselnými prvky různých typů
Prozatím víme, jakým způsobem je realizován součet vektorů s prvky typu signed char. Pokusme se nyní o realizaci součtu vektorů s celočíselnými prvky jiných typů (s odlišnou bitovou šířkou a popř. bez znaménka). Předchozí demonstrační příklad bude rozšířen do podoby:
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 int i64x2 __attribute__((vector_size(16)));
typedef unsigned long int u64x2 __attribute__((vector_size(16)));
i8x16 add_i8x16(i8x16 x, i8x16 y) {
return x+y;
}
u8x16 add_u8x16(u8x16 x, u8x16 y) {
return x+y;
}
i16x8 add_i16x8(i16x8 x, i16x8 y) {
return x+y;
}
u16x8 add_u16x8(u16x8 x, u16x8 y) {
return x+y;
}
i32x4 add_i32x4(i32x4 x, i32x4 y) {
return x+y;
}
u32x4 add_u32x4(u32x4 x, u32x4 y) {
return x+y;
}
i64x2 add_i64x2(i64x2 x, i64x2 y) {
return x+y;
}
u64x2 add_u64x2(u64x2 x, u64x2 y) {
return x+y;
}
Ve výsledném bajtkódu můžeme vidět různé varianty vektorových instrukcí pro součet, u kterých se však (logicky) nerozlišuje mezi tím, zda jsou prvky vektorů se znaménkem nebo bez znaménka:
simd_7.wasm: file format wasm 0x1 Code Disassembly: 00004a func[0] <add_i8x16>: 00004b: 20 01 | local.get 1 00004d: 20 00 | local.get 0 00004f: fd 6e | i8x16.add 000051: 0b | end 000053 func[1] <add_u8x16>: 000054: 20 01 | local.get 1 000056: 20 00 | local.get 0 000058: fd 6e | i8x16.add 00005a: 0b | end 00005c func[2] <add_i16x8>: 00005d: 20 01 | local.get 1 00005f: 20 00 | local.get 0 000061: fd 8e 01 | i16x8.add 000064: 0b | end 000066 func[3] <add_u16x8>: 000067: 20 01 | local.get 1 000069: 20 00 | local.get 0 00006b: fd 8e 01 | i16x8.add 00006e: 0b | end 000070 func[4] <add_i32x4>: 000071: 20 01 | local.get 1 000073: 20 00 | local.get 0 000075: fd ae 01 | i32x4.add 000078: 0b | end 00007a func[5] <add_u32x4>: 00007b: 20 01 | local.get 1 00007d: 20 00 | local.get 0 00007f: fd ae 01 | i32x4.add 000082: 0b | end 000084 func[6] <add_i64x2>: 000085: 20 01 | local.get 1 000087: 20 00 | local.get 0 000089: fd ae 01 | i32x4.add 00008c: 0b | end 00008e func[7] <add_u64x2>: 00008f: 20 01 | local.get 1 000091: 20 00 | local.get 0 000093: fd ae 01 | i32x4.add 000096: 0b | end
14. Součet vektorů s prvky s plovoucí řádovou čárkou
Vektory mohou obsahovat prvky s plovoucí řádovou čárkou. Datové typy jednotlivých prvků mohou být s poloviční přesností (half float, k tomuto tématu se ještě vrátíme), s jednoduchou přesností (single) i s dvojitou přesností (double). To si pochopitelně můžeme snadno ověřit:
typedef float f32x4 __attribute__((vector_size(16)));
typedef double f64x2 __attribute__((vector_size(16)));
typedef __fp16 f16x8 __attribute__((vector_size(16)));
f16x8 add_f16x8(f16x8 x, f16x8 y) {
return x+y;
}
f32x4 add_f32x4(f32x4 x, f32x4 y) {
return x+y;
}
f64x2 add_f64x2(f64x2 x, f64x2 y) {
return x+y;
}
Překlad do WebAssembly dopadne následovně:
simd_8.wasm: file format wasm 0x1 Code Disassembly: 000045 func[0] <add_f16x8>: 000046: 20 01 | local.get 1 000048: 20 00 | local.get 0 00004a: fd 8e 01 | i16x8.add 00004d: 0b | end 00004f func[1] <add_f32x4>: 000050: 20 00 | local.get 0 000052: 20 01 | local.get 1 000054: fd e4 01 | f32x4.add 000057: 0b | end 000059 func[2] <add_f64x2>: 00005a: 20 00 | local.get 0 00005c: 20 01 | local.get 1 00005e: fd f0 01 | f64x2.add 000061: 0b | end
15. Všechny instrukce pro součet vektorů
Nyní si již můžeme vytvořit soupis všech instrukcí WebAssembly, které dokážou sečíst dvojici vektorů. Všechny tyto instrukce byly použity v předchozích demonstračních příkladech:
| 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 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ů) |
16. Další celočíselné operace prováděné s prvky vektorů
Pokusme se o implementaci dalších základních aritmetických operací prováděných s prvky vektorů. Použijeme vektory se čtyřmi 32bitovými prvky a budeme implementovat operaci změny znaménka a dále součet (ten už známe), rozdíl, součin i podíl:
typedef unsigned int i32x4 __attribute__((vector_size(16)));
i32x4 neg(i32x4 x) {
return -x;
}
i32x4 add(i32x4 x, i32x4 y) {
return x+y;
}
i32x4 sub(i32x4 x, i32x4 y) {
return x-y;
}
i32x4 mul(i32x4 x, i32x4 y) {
return x*y;
}
i32x4 div(i32x4 x, i32x4 y) {
return x/y;
}
Většina operací bude přeložena do jediné instrukce WebAssembly, ovšem s výjimkou podílu (k tomu, proč výsledek vypadá tak jak vypadá, se vrátíme příště):
simd_9.wasm: file format wasm 0x1 Code Disassembly: 00004c func[0] <neg>: 00004d: 20 00 | local.get 0 00004f: fd a1 01 | i32x4.neg 000052: 0b | end 000054 func[1] <add>: 000055: 20 01 | local.get 1 000057: 20 00 | local.get 0 000059: fd ae 01 | i32x4.add 00005c: 0b | end 00005e func[2] <sub>: 00005f: 20 00 | local.get 0 000061: 20 01 | local.get 1 000063: fd b1 01 | i32x4.sub 000066: 0b | end 000068 func[3] <mul>: 000069: 20 01 | local.get 1 00006b: 20 00 | local.get 0 00006d: fd b5 01 | i32x4.mul 000070: 0b | end 000072 func[4] <div>: 000073: 20 00 | local.get 0 000075: fd 1b 00 | i32x4.extract_lane 0 000078: 20 01 | local.get 1 00007a: fd 1b 00 | i32x4.extract_lane 0 00007d: 6e | i32.div_u 00007e: fd 11 | i32x4.splat 000080: 20 00 | local.get 0 000082: fd 1b 01 | i32x4.extract_lane 1 000085: 20 01 | local.get 1 000087: fd 1b 01 | i32x4.extract_lane 1 00008a: 6e | i32.div_u 00008b: fd 1c 01 | i32x4.replace_lane 1 00008e: 20 00 | local.get 0 000090: fd 1b 02 | i32x4.extract_lane 2 000093: 20 01 | local.get 1 000095: fd 1b 02 | i32x4.extract_lane 2 000098: 6e | i32.div_u 000099: fd 1c 02 | i32x4.replace_lane 2 00009c: 20 00 | local.get 0 00009e: fd 1b 03 | i32x4.extract_lane 3 0000a1: 20 01 | local.get 1 0000a3: fd 1b 03 | i32x4.extract_lane 3 0000a6: 6e | i32.div_u 0000a7: fd 1c 03 | i32x4.replace_lane 3 0000aa: 0b | end
17. Obsah navazujícího článku
Ve skutečnosti je ve WebAssembly definováno přibližně dvě stě různých vektorových instrukcí. Některé z těchto instrukcí si popíšeme v navazujícím článku.
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 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 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ů) |
| 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/
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

