Obsah
1. Pohled pod kapotu formátu WebAssembly: základní instrukční sada
2. Základní numerické datové typy
3. Instrukce pro manipulaci s hodnotami na zásobníku operandů
4. Otestování výše popsaných instrukcí
5. Celočíselné aritmetické instrukce
6. Demonstrační příklady: použití aritmetických instrukcí
7. Aritmetické instrukce zpracovávající hodnoty s plovoucí řádovou čárkou
8. Demonstrační příklady: použití aritmetických instrukcí pro FP hodnoty
9. Instrukce pro porovnání celočíselných hodnot
10. Demonstrační příklady – porovnávání celočíselných hodnot
11. Instrukce pro porovnání hodnot s plovoucí řádovou čárkou
12. Demonstrační příklady – porovnávání hodnot s plovoucí řádovou čárkou
14. Demonstrační příklady – celočíselné konverze
15. Demonstrační příklady – konverze hodnot s plovoucí řádovou čárkou
16. Demonstrační příklady – konverze celočíselných hodnot na hodnoty s plovoucí řádovou čárkou
17. Demonstrační příklady – konverze hodnot s plovoucí řádovou čárkou na celočíselné hodnoty
18. Vysvětlení způsobu realizace konverzí
19. Tabulka se všemi doposud popsanými instrukcemi
1. Pohled pod kapotu formátu WebAssembly: základní instrukční sada
Na úvodní článek o technologii WebAssembly dnes navážeme. Popíšeme si totiž většinu základních instrukcí, které jsou ve WebAssembly definovány. Jedná se o aritmetické instrukce, konverzní operace, porovnání operandů, řízení toku programu a taktéž o instrukce určené pro manipulaci s obsahem zásobníku operandů (operand stack). Všechny aplikace překládané do WebAssembly (například přes nástroj Emscripten, přímo překladačem jazyka Go atd.) tyto instrukce používají.
Bajtkód pro WebAssembly budeme generovat s využitím Clangu (což je překladač jazyka C, C++ atd.), dále nástroje llc (backend překladač LLVM) a taktéž nástroje wasm-objdump, který je součástí balíčku nástrojů WABT: WebAssembly Binary Toolkit, s nimiž jsme se taktéž minule seznámili.
Všechny dále popsané demonstrační příklady mají svoji zdrojovou podobu psanou v čistém jazyku C. Překlad do WebAssembly s výpisem obsahu bajtkódu zajišťuje následující shell skript:
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
Již minule jsme se zmínili o několika instrukcích WebAssembly. Ty se používají pro základní řízení toku programu – volání funkce a návrat z funkce (či z libovolného bloku). Tyto instrukce budou použity i v dalších kapitolách (již bez podrobnějšího popisu):
| Operační kód | Jméno instrukce | Stručný popis |
|---|---|---|
| 0×10 | call | zavolání funkce |
| 0×0b | end | konec bloku nebo konec celé funkce |
| 0×0f | return | ukončení funkce s předáním návratových hodnot přes zásobník |
2. Základní numerické datové typy
Mnoho instrukcí WebAssembly, s nimiž se setkáme v navazujících kapitolách, začíná prefixem, který určuje typ hodnot, s nimiž bude instrukce pracovat. Příkladem může být instrukce add, která ve skutečnosti existuje ve čtyřech variantách (každá z nich má jiný operační kód). Prefix se zapisuje před jméno instrukce (proto je to prefix) a je od něj oddělen tečkou:
| i32 | 32bitové celé číslo (se znaménkem i bez znaménka) |
| i64 | 64bitové celé číslo (se znaménkem i bez znaménka) |
| f32 | 32bitové číslo s plovoucí řádovou čárkou (single, float) |
| f64 | 64bitové číslo s plovoucí řádovou čárkou (double) |
3. Instrukce pro manipulaci s hodnotami na zásobníku operandů
V prakticky všech kódech přeložených do (bajtkóud) WebAssembly se setkáme s některou z následujících instrukcí. Tyto instrukce jsou určeny pro manipulaci s hodnotami uloženými na zásobníku operandů (operand stack):
| Operační kód | Jméno instrukce | Stručný popis |
|---|---|---|
| 0×1a | drop | odstranění hodnoty nebo hodnot ze zásobníku operandů |
| 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×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×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 |
4. Otestování výše popsaných instrukcí
Nyní si ukážeme příklady použití některých z instrukcí zmíněných v předchozí kapitole. Nejprve si otestujeme překlad jednoduché céčkovské funkce provádějící výpočet s celočíselnými parametry:
int inc(int a) {
return a + 1;
}
V tomto případě se na zásobník operandů uloží první (jediný) parametr, který je chápán jako lokální proměnná (je na zásobníkovém rámci), dále se uloží konstanta 1, provede se součet (viz další kapitoly) a funkce je ukončena instrukcí end, která vrátí celý obsah zásobníku operandů:
000042 func[0] <inc>: 000043: 20 00 | local.get 0 000045: 41 01 | i32.const 1 000047: 6a | i32.add 000048: 0b | end
Překlad dalších (velmi jednoduchých) céčkovských funkcí do WebAssembly:
int return_constant() {
return 42;
}
int x(int);
int call_x(int a) {
return x(a);
}
int first(int a, int b) {
return a;
}
int second(int a, int b) {
return b;
}
int try_return(int a) {
if (a<0) {
return 42;
}
return 9999;
}
Výsledek překladu by měl vypadat následovně:
stack.wasm: file format wasm 0x1 Code Disassembly: 000058 func[1] <return_constant>: 000059: 41 2a | i32.const 42 00005b: 0b | end 00005d func[2] <call_x>: 00005e: 20 00 | local.get 0 000060: 10 80 80 80 80 00 | call 0 <env.x> 000066: 0b | end 000068 func[3] <first>: 000069: 20 00 | local.get 0 00006b: 0b | end 00006d func[4] <second>: 00006e: 20 01 | local.get 1 000070: 0b | end 000072 func[5] <try_return>: 000073: 41 2a | i32.const 42 000075: 41 8f ce 00 | i32.const 9999 000079: 20 00 | local.get 0 00007b: 41 00 | i32.const 0 00007d: 48 | i32.lt_s 00007e: 1b | select 00007f: 0b | end
Podrobnější popis je možná nutný u poslední funkce, u níž je podmínka a<0 přeložena do sekvence tří instrukcí:
000079: 20 00 | local.get 0 00007b: 41 00 | i32.const 0 00007d: 48 | i32.lt_s
Funkce má vracet hodnotu 42 nebo 9999. To je vyřešeno uložením obou těchto konstant na zásobník, výpočet výsledku podmínky (1 nebo 0) a zavolání instrukce select:
000073: 41 2a | i32.const 42
000075: 41 8f ce 00 | i32.const 9999
... vyhodnocení podmínky ...
... vyhodnocení podmínky ...
... vyhodnocení podmínky ...
00007e: 1b | select
5. Celočíselné aritmetické instrukce
V instrukčním souboru WebAssembly nalezneme i všechny základní aritmetické instrukce, které akceptují celočíselné operandy typu 32bitové nebo 64bitové celé číslo bez znaménka popř. se znaménkem. Znaménko je ve skutečnosti rozpoznáváno pouze u některých instrukcí (bude uvedeno v jejich popisu, u ostatních instrukcí nemá znaménko vliv na výsledky). Všechny instrukce očekávají své operandy na zásobníku operandů, kam se vrací i výsledek provedeného výpočtu:
| Operační kód | Jméno instrukce | Stručný popis |
|---|---|---|
| 0×6a | i32.add | součet dvou celých 32bitových hodnot |
| 0×7c | i64.add | součet dvou celých 64bitových hodnot |
| 0×6b | i32.sub | rozdíl dvou celých 32bitových hodnot |
| 0×7d | i64.sub | rozdíl dvou celých 64bitových hodnot |
| 0×6c | i32.mul | součin dvou celých 32bitových hodnot |
| 0×7e | i64.mul | součin dvou celých 64bitových hodnot |
| 0×6d | i32.div_s | podíl dvou celých 32bitových hodnot se znaménkem |
| 0×7f | i64.div_s | podíl dvou celých 64bitových hodnot se znaménkem |
| 0×6e | i32.div_u | podíl dvou celých 32bitových hodnot bez znaménka |
| 0×80 | i64.div_u | podíl dvou celých 64bitových hodnot bez znaménka |
| 0×6f | i32.rem_s | zbytek po dělení dvou celých 32bitových hodnot se znaménkem |
| 0×81 | i64.rem_s | zbytek po dělení dvou celých 64bitových hodnot se znaménkem |
| 0×70 | i32.rem_u | zbytek po dělení dvou celých 32bitových hodnot bez znaménka |
| 0×82 | i64.rem_u | zbytek po dělení dvou celých 64bitových hodnot bez znaménka |
6. Demonstrační příklady: použití aritmetických instrukcí
Všechny výše uvedené instrukce pro provedení základních aritmetických operací s celočíselnými operandy je relativně snadné otestovat, protože všechny z těchto instrukcí mají svůj protějšek v operátorech programovacího jazyka C. Nejprve vyzkoušíme 32bitové operace (stále provádíme překlad Clangem na platformě x86–64):
int iadd(int a, int b) {
return a + b;
}
int isub(int a, int b) {
return a - b;
}
int imul(int a, int b) {
return a * b;
}
int idiv(int a, int b) {
return a / b;
}
int imod(int a, int b) {
return a % b;
}
unsigned int uadd(unsigned int a, unsigned int b) {
return a + b;
}
unsigned int usub(unsigned int a, unsigned int b) {
return a - b;
}
unsigned int umul(unsigned int a, unsigned int b) {
return a * b;
}
unsigned int udiv(unsigned int a, unsigned int b) {
return a / b;
}
unsigned int umod(unsigned int a, unsigned int b) {
return a % b;
}
Z výsledků překladu je patrné, že pouze u podílu a výpočtu zbytku po dělení se rozeznávají instrukce s operandy bez znaménka a se znaménkem:
arith_1.wasm: file format wasm 0x1 Code Disassembly: 00004c func[0] <iadd>: 00004d: 20 01 | local.get 1 00004f: 20 00 | local.get 0 000051: 6a | i32.add 000052: 0b | end 000054 func[1] <isub>: 000055: 20 00 | local.get 0 000057: 20 01 | local.get 1 000059: 6b | i32.sub 00005a: 0b | end 00005c func[2] <imul>: 00005d: 20 01 | local.get 1 00005f: 20 00 | local.get 0 000061: 6c | i32.mul 000062: 0b | end 000064 func[3] <idiv>: 000065: 20 00 | local.get 0 000067: 20 01 | local.get 1 000069: 6d | i32.div_s 00006a: 0b | end 00006c func[4] <imod>: 00006d: 20 00 | local.get 0 00006f: 20 01 | local.get 1 000071: 6f | i32.rem_s 000072: 0b | end 000074 func[5] <uadd>: 000075: 20 01 | local.get 1 000077: 20 00 | local.get 0 000079: 6a | i32.add 00007a: 0b | end 00007c func[6] <usub>: 00007d: 20 00 | local.get 0 00007f: 20 01 | local.get 1 000081: 6b | i32.sub 000082: 0b | end 000084 func[7] <umul>: 000085: 20 01 | local.get 1 000087: 20 00 | local.get 0 000089: 6c | i32.mul 00008a: 0b | end 00008c func[8] <udiv>: 00008d: 20 00 | local.get 0 00008f: 20 01 | local.get 1 000091: 6e | i32.div_u 000092: 0b | end 000094 func[9] <umod>: 000095: 20 00 | local.get 0 000097: 20 01 | local.get 1 000099: 70 | i32.rem_u 00009a: 0b | end
Pro „vynucení“ instrukcí pro 64bitové operandy použijeme v céčku datový typ long long:
long long iadd(long long a, long long b) {
return a + b;
}
long long isub(long long a, long long b) {
return a - b;
}
long long imul(long long a, long long b) {
return a * b;
}
long long idiv(long long a, long long b) {
return a / b;
}
long long imod(long long a, long long b) {
return a % b;
}
unsigned long long uadd(unsigned long long a, unsigned long long b) {
return a + b;
}
unsigned long long usub(unsigned long long a, unsigned long long b) {
return a - b;
}
unsigned long long umul(unsigned long long a, unsigned long long b) {
return a * b;
}
unsigned long long udiv(unsigned long long a, unsigned long long b) {
return a / b;
}
unsigned long long umod(unsigned long long a, unsigned long long b) {
return a % b;
}
Výsledek překladu do WebAssembly nyní bude vypadat následovně:
arith_2.wasm: file format wasm 0x1 Code Disassembly: 00004c func[0] <iadd>: 00004d: 20 01 | local.get 1 00004f: 20 00 | local.get 0 000051: 7c | i64.add 000052: 0b | end 000054 func[1] <isub>: 000055: 20 00 | local.get 0 000057: 20 01 | local.get 1 000059: 7d | i64.sub 00005a: 0b | end 00005c func[2] <imul>: 00005d: 20 01 | local.get 1 00005f: 20 00 | local.get 0 000061: 7e | i64.mul 000062: 0b | end 000064 func[3] <idiv>: 000065: 20 00 | local.get 0 000067: 20 01 | local.get 1 000069: 7f | i64.div_s 00006a: 0b | end 00006c func[4] <imod>: 00006d: 20 00 | local.get 0 00006f: 20 01 | local.get 1 000071: 81 | i64.rem_s 000072: 0b | end 000074 func[5] <uadd>: 000075: 20 01 | local.get 1 000077: 20 00 | local.get 0 000079: 7c | i64.add 00007a: 0b | end 00007c func[6] <usub>: 00007d: 20 00 | local.get 0 00007f: 20 01 | local.get 1 000081: 7d | i64.sub 000082: 0b | end 000084 func[7] <umul>: 000085: 20 01 | local.get 1 000087: 20 00 | local.get 0 000089: 7e | i64.mul 00008a: 0b | end 00008c func[8] <udiv>: 00008d: 20 00 | local.get 0 00008f: 20 01 | local.get 1 000091: 80 | i64.div_u 000092: 0b | end 000094 func[9] <umod>: 000095: 20 00 | local.get 0 000097: 20 01 | local.get 1 000099: 82 | i64.rem_u 00009a: 0b | end
7. Aritmetické instrukce zpracovávající hodnoty s plovoucí řádovou čárkou
Sada základních aritmetických instrukcí pro hodnoty typu single/float a double vypadá následovně. Povšimněte si, že chybí instrukce pro výpočet zbytku po dělení, ovšem navíc jsou k dispozici instrukce pro výpočet druhé odmocniny a absolutní hodnoty (používají se poměrně často), výpočet maximální a minimální hodnoty a taktéž instrukce pro změnu znaménka operandu:
| Operační kód | Jméno instrukce | Stručný popis |
|---|---|---|
| 0×92 | f32.add | součet dvou hodnot typu single/float |
| 0×a0 | f64.add | součet dvou hodnot typu double |
| 0×93 | f32.sub | rozdíl dvou hodnot typu single/float |
| 0×a1 | f64.sub | rozdíl dvou hodnot typu double |
| 0×94 | f32.mul | součin dvou hodnot typu single/float |
| 0×a2 | f64.mul | součin dvou hodnot typu double |
| 0×95 | f32.div | podíl dvou hodnot typu single/float |
| 0×a3 | f64.div | podíl dvou hodnot typu double |
| 0×91 | f32.sqrt | druhá odmocnina z hodnoty typu single/float |
| 0×9f | f64.sqrt | druhá odmocnina z hodnoty typu double |
| 0×96 | f32.min | součet dvou hodnot typu single/float |
| 0×a4 | f64.min | součet dvou hodnot typu double |
| 0×97 | f32.max | součet dvou hodnot typu single/float |
| 0×a5 | f64.max | součet dvou hodnot typu double |
| 0×8b | f32.abs | absolutní hodnota typu single/float |
| 0×99 | f64.abs | absolutní hodnota typu double |
| 0×8c | f32.neg | otočení znaménka u hodnoty typu single/float |
| 0×9a | f64.neg | otočení znaménka u hodnoty typu double |
8. Demonstrační příklady: použití aritmetických instrukcí pro FP hodnoty
Většinu instrukcí z předchozí kapitoly si opět můžeme relativně snadno otestovat překladem zdrojového kódu naprogramovaného v jazyku C. Varianta pro operandy typu single/float:
float fadd(float a, float b) {
return a + b;
}
float fsub(float a, float b) {
return a - b;
}
float fmul(float a, float b) {
return a * b;
}
float fdiv(float a, float b) {
return a / b;
}
float fsqrt(float a) {
return sqrtf(a);
}
float fabs(float a) {
return fabsf(a);
}
Výsledek překladu do WebAssembly:
arith_3.wasm: file format wasm 0x1 Code Disassembly: 00004d func[0] <fadd>: 00004e: 20 00 | local.get 0 000050: 20 01 | local.get 1 000052: 92 | f32.add 000053: 0b | end 000055 func[1] <fsub>: 000056: 20 00 | local.get 0 000058: 20 01 | local.get 1 00005a: 93 | f32.sub 00005b: 0b | end 00005d func[2] <fmul>: 00005e: 20 00 | local.get 0 000060: 20 01 | local.get 1 000062: 94 | f32.mul 000063: 0b | end 000065 func[3] <fdiv>: 000066: 20 00 | local.get 0 000068: 20 01 | local.get 1 00006a: 95 | f32.div 00006b: 0b | end 00006d func[4] <fsqrt>: 00006e: 20 00 | local.get 0 000070: 91 | f32.sqrt 000071: 0b | end 000073 func[5] <fabs>: 000074: 20 00 | local.get 0 000076: 8b | f32.abs 000077: 0b | end
Úprava pro operandy typu double vypadá takto:
double fadd(double a, double b) {
return a + b;
}
double fsub(double a, double b) {
return a - b;
}
double fmul(double a, double b) {
return a * b;
}
double fdiv(double a, double b) {
return a / b;
}
double sqrt(double x);
double fabs(double x);
double fsqrt(double a) {
return sqrt(a);
}
double fabs_(double a) {
return fabs(a);
}
Opět bude uveden výsledek překladu do WebAssembly:
arith_4.wasm: file format wasm 0x1 Code Disassembly: 00004d func[0] <fadd>: 00004e: 20 00 | local.get 0 000050: 20 01 | local.get 1 000052: a0 | f64.add 000053: 0b | end 000055 func[1] <fsub>: 000056: 20 00 | local.get 0 000058: 20 01 | local.get 1 00005a: a1 | f64.sub 00005b: 0b | end 00005d func[2] <fmul>: 00005e: 20 00 | local.get 0 000060: 20 01 | local.get 1 000062: a2 | f64.mul 000063: 0b | end 000065 func[3] <fdiv>: 000066: 20 00 | local.get 0 000068: 20 01 | local.get 1 00006a: a3 | f64.div 00006b: 0b | end 00006d func[4] <fsqrt>: 00006e: 20 00 | local.get 0 000070: 9f | f64.sqrt 000071: 0b | end 000073 func[5] <fabs_>: 000074: 20 00 | local.get 0 000076: 99 | f64.abs 000077: 0b | end
9. Instrukce pro porovnání celočíselných hodnot
Dalších dvacet instrukcí provádí porovnání dvou celočíselných operandů uložených na zásobníku. Výsledek porovnání je opět uložen na zásobník operandů:
| Operační kód | Jméno instrukce | Stručný popis |
|---|---|---|
| 0×46 | i32.eq | porovnání operandů typu int32 na relaci „rovno“ |
| 0×51 | i64.eq | porovnání operandů typu int64 na relaci „rovno“ |
| 0×47 | i32.ne | porovnání operandů typu int32 na relaci „nerovno“ |
| 0×52 | i64.ne | porovnání operandů typu int64 na relaci „nerovno“ |
| 0×48 | i32.lt_s | porovnání operandů typu int32 na relaci „menší než“ (se znaménkem) |
| 0×53 | i64.lt_s | porovnání operandů typu int64 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×54 | i64.lt_u | porovnání operandů typu int64 na relaci „menší než“ (bez znaménka) |
| 0×4c | i32.le_s | porovnání operandů typu int32 na relaci „menší nebo rovno“ (se znaménkem) |
| 0×57 | i64.le_s | porovnání operandů typu int64 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×58 | i64.le_u | porovnání operandů typu int64 na relaci „menší nebo rovno“ (bez znaménka) |
| 0×4a | i32.gt_s | porovnání operandů typu int32 na relaci „větší než“ (se znaménkem) |
| 0×55 | i64.gt_s | porovnání operandů typu int64 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×56 | i64.gt_u | porovnání operandů typu int64 na relaci „větší než“ (bez znaménka) |
| 0×4e | i32.ge_s | porovnání operandů typu int32 na relaci „větší nebo rovno“ (se znaménkem) |
| 0×59 | i64.ge_s | porovnání operandů typu int64 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×5a | i64.ge_u | porovnání operandů typu int64 na relaci „větší nebo rovno“ (bez znaménka) |
10. Demonstrační příklady – porovnávání celočíselných hodnot
Podívejme se nyní na způsob překladu relačních operátorů v případě, že jsou aplikovány na 32bitové celočíselné hodnoty se znaménkem (signed):
int eq(int a, int b) {
return a == b;
}
int ne(int a, int b) {
return a != b;
}
int lt(int a, int b) {
return a < b;
}
int le(int a, int b) {
return a <= b;
}
int gt(int a, int b) {
return a > b;
}
int ge(int a, int b) {
return a >= b;
}
Překlad do bajktódu WebAssembly pravděpodobně nevyžaduje podrobnější komentář; pouze si povšimněte, že jsou použity varianty instrukcí se suffixem _s (signed):
comparison_1.wasm: file format wasm 0x1 Code Disassembly: 000048 func[0] <eq>: 000049: 20 00 | local.get 0 00004b: 20 01 | local.get 1 00004d: 46 | i32.eq 00004e: 0b | end 000050 func[1] <ne>: 000051: 20 00 | local.get 0 000053: 20 01 | local.get 1 000055: 47 | i32.ne 000056: 0b | end 000058 func[2] <lt>: 000059: 20 00 | local.get 0 00005b: 20 01 | local.get 1 00005d: 48 | i32.lt_s 00005e: 0b | end 000060 func[3] <le>: 000061: 20 00 | local.get 0 000063: 20 01 | local.get 1 000065: 4c | i32.le_s 000066: 0b | end 000068 func[4] <gt>: 000069: 20 00 | local.get 0 00006b: 20 01 | local.get 1 00006d: 4a | i32.gt_s 00006e: 0b | end 000070 func[5] <ge>: 000071: 20 00 | local.get 0 000073: 20 01 | local.get 1 000075: 4e | i32.ge_s 000076: 0b | end
Nyní příklad přepíšeme do takové podoby, že se budou porovnávat 32bitové hodnoty bez znaménka (unsigned):
unsigned int eq(unsigned int a, unsigned int b) {
return a == b;
}
unsigned int ne(unsigned int a, unsigned int b) {
return a != b;
}
unsigned int lt(unsigned int a, unsigned int b) {
return a < b;
}
unsigned int le(unsigned int a, unsigned int b) {
return a <= b;
}
unsigned int gt(unsigned int a, unsigned int b) {
return a > b;
}
unsigned int ge(unsigned int a, unsigned int b) {
return a >= b;
}
Nyní se v bajtkódu WebAssembly použijí relační instrukce se suffixem _u:
comparison_2.wasm: file format wasm 0x1 Code Disassembly: 000048 func[0] <eq>: 000049: 20 00 | local.get 0 00004b: 20 01 | local.get 1 00004d: 46 | i32.eq 00004e: 0b | end 000050 func[1] <ne>: 000051: 20 00 | local.get 0 000053: 20 01 | local.get 1 000055: 47 | i32.ne 000056: 0b | end 000058 func[2] <lt>: 000059: 20 00 | local.get 0 00005b: 20 01 | local.get 1 00005d: 49 | i32.lt_u 00005e: 0b | end 000060 func[3] <le>: 000061: 20 00 | local.get 0 000063: 20 01 | local.get 1 000065: 4d | i32.le_u 000066: 0b | end 000068 func[4] <gt>: 000069: 20 00 | local.get 0 00006b: 20 01 | local.get 1 00006d: 4b | i32.gt_u 00006e: 0b | end 000070 func[5] <ge>: 000071: 20 00 | local.get 0 000073: 20 01 | local.get 1 000075: 4f | i32.ge_u 000076: 0b | end
Přepis demonstračního příkladu do takové podoby, aby se porovnávaly 64bitové celočíselné operandy se znaménkem:
int eq(long long a, long long b) {
return a == b;
}
int ne(long long a, long long b) {
return a != b;
}
int lt(long long a, long long b) {
return a < b;
}
int le(long long a, long long b) {
return a <= b;
}
int gt(long long a, long long b) {
return a > b;
}
int ge(long long a, long long b) {
return a >= b;
}
Ve výsledném bajtkódu se podle očekávání použijí 64bitové varianty instrukcí pro relační operace (pochopitelně opět se suffixem _s):
comparison_3.wasm: file format wasm 0x1 Code Disassembly: 000048 func[0] <eq>: 000049: 20 00 | local.get 0 00004b: 20 01 | local.get 1 00004d: 51 | i64.eq 00004e: 0b | end 000050 func[1] <ne>: 000051: 20 00 | local.get 0 000053: 20 01 | local.get 1 000055: 52 | i64.ne 000056: 0b | end 000058 func[2] <lt>: 000059: 20 00 | local.get 0 00005b: 20 01 | local.get 1 00005d: 53 | i64.lt_s 00005e: 0b | end 000060 func[3] <le>: 000061: 20 00 | local.get 0 000063: 20 01 | local.get 1 000065: 57 | i64.le_s 000066: 0b | end 000068 func[4] <gt>: 000069: 20 00 | local.get 0 00006b: 20 01 | local.get 1 00006d: 55 | i64.gt_s 00006e: 0b | end 000070 func[5] <ge>: 000071: 20 00 | local.get 0 000073: 20 01 | local.get 1 000075: 59 | i64.ge_s 000076: 0b | end
Poslední varianta demonstračního příkladu, tentokrát porovnávající 64bitové hodnoty bez znaménka:
int eq(unsigned long long a, unsigned long long b) {
return a == b;
}
int ne(unsigned long long a, unsigned long long b) {
return a != b;
}
int lt(unsigned long long a, unsigned long long b) {
return a < b;
}
int le(unsigned long long a, unsigned long long b) {
return a <= b;
}
int gt(unsigned long long a, unsigned long long b) {
return a > b;
}
int ge(unsigned long long a, unsigned long long b) {
return a >= b;
}
Podoba výsledného bajtkódu se 64bitovými variantami instrukcí se suffixem _u:
comparison_4.wasm: file format wasm 0x1 Code Disassembly: 000048 func[0] <eq>: 000049: 20 00 | local.get 0 00004b: 20 01 | local.get 1 00004d: 51 | i64.eq 00004e: 0b | end 000050 func[1] <ne>: 000051: 20 00 | local.get 0 000053: 20 01 | local.get 1 000055: 52 | i64.ne 000056: 0b | end 000058 func[2] <lt>: 000059: 20 00 | local.get 0 00005b: 20 01 | local.get 1 00005d: 54 | i64.lt_u 00005e: 0b | end 000060 func[3] <le>: 000061: 20 00 | local.get 0 000063: 20 01 | local.get 1 000065: 58 | i64.le_u 000066: 0b | end 000068 func[4] <gt>: 000069: 20 00 | local.get 0 00006b: 20 01 | local.get 1 00006d: 56 | i64.gt_u 00006e: 0b | end 000070 func[5] <ge>: 000071: 20 00 | local.get 0 000073: 20 01 | local.get 1 000075: 5a | i64.ge_u 000076: 0b | end
11. Instrukce pro porovnání hodnot s plovoucí řádovou čárkou
V instrukčním kódu WebAssembly pochopitelně nalezneme i úplný soubor instrukcí určených pro porovnání dvojice hodnot typu single/float nebo double. Výsledky porovnání se uloží na zásobník operandů:
| Operační kód | Jméno instrukce | Stručný popis |
|---|---|---|
| 0×5b | f32.eq | porovnání dvou hodnot typu single na relaci „rovno“ |
| 0×61 | f64.eq | porovnání dvou hodnot typu double na relaci „rovno“ |
| 0×5c | f32.ne | porovnání dvou hodnot typu single na relaci „nerovno“ |
| 0×62 | f64.ne | porovnání dvou hodnot typu double na relaci „nerovno“ |
| 0×5d | f32.lt | porovnání dvou hodnot typu single na relaci „menší než“ |
| 0×63 | f64.lt | porovnání dvou hodnot typu double na relaci „menší než“ |
| 0×5e | f32.gt | porovnání dvou hodnot typu single na relaci „větší než“ |
| 0×64 | f64.gt | porovnání dvou hodnot typu double na relaci „větší než“ |
| 0×5f | f32.le | porovnání dvou hodnot typu single na relaci „menší nebo rovno“ |
| 0×65 | f64.le | porovnání dvou hodnot typu double na relaci „menší nebo rovno“ |
| 0×60 | f32.ge | porovnání dvou hodnot typu single na relaci „větší nebo rovno“ |
| 0×66 | f64.ge | porovnání dvou hodnot typu double na relaci „větší nebo rovno“ |
12. Demonstrační příklady – porovnávání hodnot s plovoucí řádovou čárkou
Následují ukázky překladu funkcí, které provádí porovnání dvojice hodnot (vždy stejného typu) s plovoucí řádovou čárkou. V tomto případě je situace jednodušší, než u hodnot celočíselných, protože není nutné rozlišovat porovnání hodnot bez znaménka a se znaménkem.
Porovnání hodnot s jednoduchou přesností:
int eq(float a, float b) {
return a == b;
}
int ne(float a, float b) {
return a != b;
}
int lt(float a, float b) {
return a < b;
}
int le(float a, float b) {
return a <= b;
}
int gt(float a, float b) {
return a > b;
}
int ge(float a, float b) {
return a >= b;
}
Překlad do bajtkódu WebAssembly:
comparison_5.wasm: file format wasm 0x1 Code Disassembly: 000048 func[0] <eq>: 000049: 20 00 | local.get 0 00004b: 20 01 | local.get 1 00004d: 5b | f32.eq 00004e: 0b | end 000050 func[1] <ne>: 000051: 20 00 | local.get 0 000053: 20 01 | local.get 1 000055: 5c | f32.ne 000056: 0b | end 000058 func[2] <lt>: 000059: 20 00 | local.get 0 00005b: 20 01 | local.get 1 00005d: 5d | f32.lt 00005e: 0b | end 000060 func[3] <le>: 000061: 20 00 | local.get 0 000063: 20 01 | local.get 1 000065: 5f | f32.le 000066: 0b | end 000068 func[4] <gt>: 000069: 20 00 | local.get 0 00006b: 20 01 | local.get 1 00006d: 5e | f32.gt 00006e: 0b | end 000070 func[5] <ge>: 000071: 20 00 | local.get 0 000073: 20 01 | local.get 1 000075: 60 | f32.ge 000076: 0b | end
Podobný příklad, nyní ovšem porovnávající hodnoty s dvojitou přesností:
int eq(double a, double b) {
return a == b;
}
int ne(double a, double b) {
return a != b;
}
int lt(double a, double b) {
return a < b;
}
int le(double a, double b) {
return a <= b;
}
int gt(double a, double b) {
return a > b;
}
int ge(double a, double b) {
return a >= b;
}
Překlad do bajtkódu WebAssembly:
comparison_6.wasm: file format wasm 0x1 Code Disassembly: 000048 func[0] <eq>: 000049: 20 00 | local.get 0 00004b: 20 01 | local.get 1 00004d: 61 | f64.eq 00004e: 0b | end 000050 func[1] <ne>: 000051: 20 00 | local.get 0 000053: 20 01 | local.get 1 000055: 62 | f64.ne 000056: 0b | end 000058 func[2] <lt>: 000059: 20 00 | local.get 0 00005b: 20 01 | local.get 1 00005d: 63 | f64.lt 00005e: 0b | end 000060 func[3] <le>: 000061: 20 00 | local.get 0 000063: 20 01 | local.get 1 000065: 65 | f64.le 000066: 0b | end 000068 func[4] <gt>: 000069: 20 00 | local.get 0 00006b: 20 01 | local.get 1 00006d: 64 | f64.gt 00006e: 0b | end 000070 func[5] <ge>: 000071: 20 00 | local.get 0 000073: 20 01 | local.get 1 000075: 66 | f64.ge 000076: 0b | end
13. Konverzní instrukce
V praxi je pochopitelně nutné konvertovat operandy různých typů na jiný datový typ (a to buď s potenciální ztrátou přesnosti nebo přetečením hodnot). Těchto konverzí existuje celá řada. Instrukce, které konverze provádí, jsou uvedeny v další tabulce. Povšimněte si, že je tato skupina instrukcí poněkud nesourodá, resp. ne tak pravidelná, jako tomu bylo u instrukcí z předchozích skupin:
| Operační kód | Jméno instrukce | Stručný popis |
|---|---|---|
| 0×a7 | i32.wrap_i64 | de facto opak instrukce extend, převod hodnoty se ztrátou informace 64 na 32 bitů |
| 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×a8 | i32.trunc_f32_s | převod hodnoty typu float na celé číslo se znaménkem |
| 0×aa | i32.trunc_f64_s | převod hodnoty typu double na celé číslo se znaménkem |
| 0×ae | i64.trunc_f32_s | převod hodnoty typu float na celé číslo se znaménkem |
| 0×b0 | i64.trunc_f64_s | převod hodnoty typu double na celé číslo se znaménkem |
| 0×a9 | i32.trunc_f32_u | převod hodnoty typu float na celé číslo bez znaménka |
| 0×ab | i32.trunc_f64_u | převod hodnoty typu double na celé číslo bez znaménka |
| 0×af | i64.trunc_f32_u | převod hodnoty typu float na celé číslo bez znaménka |
| 0×b1 | i64.trunc_f64_u | převod hodnoty typu double na celé číslo bez znaménka |
| 0×b6 | f32.demote_f64 | převod hodnoty typu double na typ float |
| 0×bb | f64.promote_f32 | převod hodnoty typu float na typ double |
| 0×b2 | f32.convert_i32_s | konverze celého čísla se znaménkem (32 bitů) na typ float |
| 0×b4 | f32.convert_i64_s | konverze celého čísla se znaménkem (32 bitů) na typ float |
| 0×b7 | f64.convert_i32_s | konverze celého čísla se znaménkem (64 bitů) na typ double |
| 0×b9 | f64.convert_i64_s | konverze celého čísla se znaménkem (64 bitů) na typ double |
| 0×b3 | f32.convert_i32_u | konverze celého čísla bez znaménka (32 bitů) na typ float |
| 0×b5 | f32.convert_i64_u | konverze celého čísla bez znaménka (32 bitů) na typ float |
| 0×b8 | f64.convert_i32_u | konverze celého čísla bez znaménka (64 bitů) na typ double |
| 0×ba | f64.convert_i64_u | konverze celého čísla bez znaménka (64 bitů) 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ů |
14. Demonstrační příklady – celočíselné konverze
Nejprve si ukažme způsob překladu operací, při kterých se konvertují celočíselná data, do WebAssembly. Varianty konverzí beze ztráty rozsahu:
long long from_int(int a) {
return a;
}
long long from_uint(unsigned int a) {
return a;
}
Výsledek překladu do WebAssembly ukazuje přímé použití instrukce i64.extend_i32_X:
000043 func[0] <from_int>: 000044: 20 00 | local.get 0 000046: ac | i64.extend_i32_s 000047: 0b | end 000049 func[1] <from_uint>: 00004a: 20 00 | local.get 0 00004c: ad | i64.extend_i32_u 00004d: 0b | end
Konverze z hodnoty s větším počtem bitů na hodnotu s menším počtem bitů:
char char_from_int(int x) {
return x;
}
short short_from_int(int x) {
return x;
}
char char_from_long(long long x) {
return x;
}
short short_from_long(long long x) {
return x;
}
int int_from_long(long long x) {
return x;
}
Nyní se použijí instrukce extend8|16_s, což vypadá divně, ovšem musíme si uvědomit, že z přeložených funkcí se vždy vrací hodnoty typu i32 nebo i64, takže se vlastně stále vrací celé slovo, ovšem jen ve spodních osmi, 16 nebo 32 bitech je hodnota se znaménkem:
conversions_2.wasm: file format wasm 0x1 Code Disassembly: 00004b func[0] <char_from_int>: 00004c: 20 00 | local.get 0 00004e: c0 | i32.extend8_s 00004f: 0b | end 000051 func[1] <short_from_int>: 000052: 20 00 | local.get 0 000054: c1 | i32.extend16_s 000055: 0b | end 000057 func[2] <char_from_long>: 000058: 20 00 | local.get 0 00005a: a7 | i32.wrap_i64 00005b: c0 | i32.extend8_s 00005c: 0b | end 00005e func[3] <short_from_long>: 00005f: 20 00 | local.get 0 000061: a7 | i32.wrap_i64 000062: c1 | i32.extend16_s 000063: 0b | end 000065 func[4] <int_from_long>: 000066: 20 00 | local.get 0 000068: a7 | i32.wrap_i64 000069: 0b | end
Podobné příklady, ovšem s konverzí hodnot bez znaménka:
unsigned char char_from_int(unsigned int x) {
return x;
}
unsigned short short_from_int(unsigned int x) {
return x;
}
unsigned char char_from_long(unsigned long long x) {
return x;
}
unsigned short short_from_long(unsigned long long x) {
return x;
}
unsigned int int_from_long(unsigned long long x) {
return x;
}
Zajímavé je, že nyní LLVM zvolil naprosto odlišný (a více pochopitelný) způsob překladu a namísto konverze jen ořezává vstupní hodnoty na zadaný počet bitů (8, 16, 32):
conversions_3.wasm: file format wasm 0x1 Code Disassembly: 00004b func[0] <char_from_int>: 00004c: 20 00 | local.get 0 00004e: 41 ff 01 | i32.const 255 000051: 71 | i32.and 000052: 0b | end 000054 func[1] <short_from_int>: 000055: 20 00 | local.get 0 000057: 41 ff ff 03 | i32.const 65535 00005b: 71 | i32.and 00005c: 0b | end 00005e func[2] <char_from_long>: 00005f: 20 00 | local.get 0 000061: a7 | i32.wrap_i64 000062: 41 ff 01 | i32.const 255 000065: 71 | i32.and 000066: 0b | end 000068 func[3] <short_from_long>: 000069: 20 00 | local.get 0 00006b: a7 | i32.wrap_i64 00006c: 41 ff ff 03 | i32.const 65535 000070: 71 | i32.and 000071: 0b | end 000073 func[4] <int_from_long>: 000074: 20 00 | local.get 0 000076: a7 | i32.wrap_i64 000077: 0b | end
15. Demonstrační příklady – konverze hodnot s plovoucí řádovou čárkou
Další sada konverzních instrukcí konvertuje data mezi hodnotami typu float a double. Situace je tedy mnohem jednodušší, protože se mohou provést jen dva typy konverzí:
double from_float(float x) {
return x;
}
float to_float(double x) {
return x;
}
Překlad do WebAssembly vede k použití instrukcí f64.promote_f32 a f32.demote_f64:
conversions_4.wasm: file format wasm 0x1 Code Disassembly: 000048 func[0] <from_float>: 000049: 20 00 | local.get 0 00004b: bb | f64.promote_f32 00004c: 0b | end 00004e func[1] <to_float>: 00004f: 20 00 | local.get 0 000051: b6 | f32.demote_f64 000052: 0b | end
16. Demonstrační příklady – konverze celočíselných hodnot na hodnoty s plovoucí řádovou čárkou
Další konverze již zahrnují kombinaci celočíselných hodnot a hodnot s plovoucí řádovou čárkou. Nejprve si vyzkoušíme konverze celočíselných hodnot (s různou bitovou šířkou) na hodnoty typu float nebo double. Samotný zdrojový kód příkladu napsaný v C je přímočarý:
float float_from_int(int x) {
return x;
}
double double_from_int(int x) {
return x;
}
float float_from_long(long long x) {
return x;
}
double double_from_long(long long x) {
return x;
}
float float_from_unsigned_int(unsigned int x) {
return x;
}
double double_from_unsigned_int(unsigned int x) {
return x;
}
float float_from_unsigned_long(unsigned long long x) {
return x;
}
double double_from_unsigned_long(unsigned long long x) {
return x;
}
Všechny konverze jsou postaveny na instrukci f32|f64.convert_i32|64_s|u:
conversions_5.wasm: file format wasm 0x1 Code Disassembly: 000058 func[0] <float_from_int>: 000059: 20 00 | local.get 0 00005b: b2 | f32.convert_i32_s 00005c: 0b | end 00005e func[1] <double_from_int>: 00005f: 20 00 | local.get 0 000061: b7 | f64.convert_i32_s 000062: 0b | end 000064 func[2] <float_from_long>: 000065: 20 00 | local.get 0 000067: b4 | f32.convert_i64_s 000068: 0b | end 00006a func[3] <double_from_long>: 00006b: 20 00 | local.get 0 00006d: b9 | f64.convert_i64_s 00006e: 0b | end 000070 func[4] <float_from_unsigned_int>: 000071: 20 00 | local.get 0 000073: b3 | f32.convert_i32_u 000074: 0b | end 000076 func[5] <double_from_unsigned_int>: 000077: 20 00 | local.get 0 000079: b8 | f64.convert_i32_u 00007a: 0b | end 00007c func[6] <float_from_unsigned_long>: 00007d: 20 00 | local.get 0 00007f: b5 | f32.convert_i64_u 000080: 0b | end 000082 func[7] <double_from_unsigned_long>: 000083: 20 00 | local.get 0 000085: ba | f64.convert_i64_u 000086: 0b | end
17. Demonstrační příklady – konverze hodnot s plovoucí řádovou čárkou na celočíselné hodnoty
Opačné konverze, konkrétně konverze hodnot s plovoucí řádovou čárkou na celočíselné hodnoty, sice v jazyku C vypadají podobně, ovšem (jak uvidíme dále) je způsob jejich překladu velmi odlišný:
int int_from_float(float x) {
return x;
}
long long long_from_float(float x) {
return x;
}
int int_from_double(double x) {
return x;
}
long long long_from_double(double x) {
return x;
}
Tyto velmi jednoduché funkce jsou ve skutečnosti přeloženy poměrně složitým způsobem – s testem na hodnoty větší, než je povolený rozsah i32 nebo i64, hodnoty menší než 0 atd.:
conversions_7.wasm: file format wasm 0x1 Code Disassembly: 000054 func[0] <unsigned_int_from_float>: 000055: 02 40 | block 000057: 20 00 | local.get 0 000059: 43 00 00 80 4f | f32.const 0x1p+32 00005e: 5d | f32.lt 00005f: 20 00 | local.get 0 000061: 43 00 00 00 00 | f32.const 0x0p+0 000066: 60 | f32.ge 000067: 71 | i32.and 000068: 45 | i32.eqz 000069: 0d 00 | br_if 0 00006b: 20 00 | local.get 0 00006d: a9 | i32.trunc_f32_u 00006e: 0f | return 00006f: 0b | end 000070: 41 00 | i32.const 0 000072: 0b | end 000074 func[1] <unsigned_long_from_float>: 000075: 02 40 | block 000077: 20 00 | local.get 0 000079: 43 00 00 80 5f | f32.const 0x1p+64 00007e: 5d | f32.lt 00007f: 20 00 | local.get 0 000081: 43 00 00 00 00 | f32.const 0x0p+0 000086: 60 | f32.ge 000087: 71 | i32.and 000088: 45 | i32.eqz 000089: 0d 00 | br_if 0 00008b: 20 00 | local.get 0 00008d: af | i64.trunc_f32_u 00008e: 0f | return 00008f: 0b | end 000090: 42 00 | i64.const 0 000092: 0b | end 000094 func[2] <unsigned_int_from_double>: 000095: 02 40 | block 000097: 20 00 | local.get 0 000099: 44 00 00 00 00 00 00 f0 41 | f64.const 0x1p+32 0000a2: 63 | f64.lt 0000a3: 20 00 | local.get 0 0000a5: 44 00 00 00 00 00 00 00 00 | f64.const 0x0p+0 0000ae: 66 | f64.ge 0000af: 71 | i32.and 0000b0: 45 | i32.eqz 0000b1: 0d 00 | br_if 0 0000b3: 20 00 | local.get 0 0000b5: ab | i32.trunc_f64_u 0000b6: 0f | return 0000b7: 0b | end 0000b8: 41 00 | i32.const 0 0000ba: 0b | end 0000bc func[3] <unsigned_long_from_double>: 0000bd: 02 40 | block 0000bf: 20 00 | local.get 0 0000c1: 44 00 00 00 00 00 00 f0 43 | f64.const 0x1p+64 0000ca: 63 | f64.lt 0000cb: 20 00 | local.get 0 0000cd: 44 00 00 00 00 00 00 00 00 | f64.const 0x0p+0 0000d6: 66 | f64.ge 0000d7: 71 | i32.and 0000d8: 45 | i32.eqz 0000d9: 0d 00 | br_if 0 0000db: 20 00 | local.get 0 0000dd: b1 | i64.trunc_f64_u 0000de: 0f | return 0000df: 0b | end 0000e0: 42 00 | i64.const 0 0000e2: 0b | end
18. Vysvětlení způsobu realizace konverzí
Výše uvedené příklady překladu do WebAssembly naznačují, že konverze float či double na celočíselné hodnoty není jednoduchý. Jaké operace se vlastně provádí, nám naznačí výsledek zpětného překladu (dekompilace):
$ wasm-decompile conversions_7.wasm
Z výsledků je patrné, že pro příliš velké hodnoty se vrátí nejmenší celé číslo (eqz vlastně převrací podmínku, resp. její výsledek):
import memory env_linear_memory;
function from_float(a:float):int {
if (eqz(abs(a) < 2147483648.0f)) goto B_a;
return i32_trunc_f32_s(a);
label B_a:
return -2147483648;
}
function long_from_float(a:float):long {
if (eqz(abs(a) < 9223372036854775808.0f)) goto B_a;
return i64_trunc_f32_s(a);
label B_a:
return -9223372036854775808L;
}
function from_double(a:double):int {
if (eqz(abs(a) < 2147483648.0)) goto B_a;
return i32_trunc_f64_s(a);
label B_a:
return -2147483648;
}
function long_from_double(a:double):long {
if (eqz(abs(a) < 9223372036854775808.0)) goto B_a;
return i64_trunc_f64_s(a);
label B_a:
return -9223372036854775808L;
}
Pro převody na celočíselné hodnoty bez znaménka se navíc testuje i fakt, zda není vstupní hodnota záporná:
import memory env_linear_memory;
function from_float(a:float):int {
if (eqz(a < 4294967296.0f & a >= 0.0f)) goto B_a;
return i32_trunc_f32_u(a);
label B_a:
return 0;
}
function long_from_float(a:float):long {
if (eqz(a < 18446744073709551616.0f & a >= 0.0f)) goto B_a;
return i64_trunc_f32_u(a);
label B_a:
return 0L;
}
function from_double(a:double):int {
if (eqz(a < 4294967296.0 & a >= 0.0)) goto B_a;
return i32_trunc_f64_u(a);
label B_a:
return 0;
}
function long_from_double(a:double):long {
if (eqz(a < 18446744073709551616.0 & a >= 0.0)) goto B_a;
return i64_trunc_f64_u(a);
label B_a:
return 0L;
}
19. 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 | ||
| 0×03 | ||
| 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 | ||
| 0×0d | ||
| 0×0e | ||
| 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 | ||
| 0×fd | ||
| 0×fe | ||
| 0×ff |
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
