Pohled pod kapotu formátu WebAssembly: základní instrukční sada

20. 11. 2025
Doba čtení: 40 minut

Sdílet

Pracovníci rozebírají vnitřek PC
Autor: Shutterstock
Popíšeme si základní instrukce, které jsou ve WebAssembly definovány. Jedná se o aritmetické instrukce, konverzní operace, porovnání operandů a taktéž o instrukce určené pro manipulaci s obsahem zásobníku operandů.

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

13. Konverzní instrukce

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

20. Odkazy na Internetu

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
Poznámka: pro překlad do mezikódu bylo nutné použít přepínač -Os, protože výchozí režim překladu (bez optimalizací) generuje nadbytečné instrukce, takže je výsledný bajtkód zbytečně dlouhý a taktéž relativně špatně čitelný.

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
Poznámka: u instrukcí končících postfixem .const je konstanta uložena ihned za operačním kódem instrukce.

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
Poznámka: to je poměrně elegantní kód, který nevyžaduje podmíněné skoky.

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
Poznámka: navíc je přesně určeno chování v nedefinovaných případech – dělení nulou, přetečení, pokus o změna znaménka nejmenší hodnoty atd.

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
Poznámka: operandy se opět pochopitelně načítají ze zásobníku operandů a výsledky se ukládají zpět na zásobník operandů.

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):

Školení Hacking

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

  1. Compiling C to WebAssembly without Emscripten
    https://surma.dev/things/c-to-webassembly/
  2. Web Assemply: Text Format
    https://webassembly.github­.io/spec/core/text/index.html
  3. WebAssembly: Binary Format
    https://webassembly.github­.io/spec/core/binary/index­.html
  4. WebAssembly
    https://webassembly.org/
  5. WebAssembly na Wiki Golangu
    https://github.com/golang/go/wi­ki/WebAssembly
  6. The future of WebAssembly – A look at upcoming features and proposals
    https://blog.scottlogic.com/2018/07/20/wasm-future.html
  7. WebAssembly Design
    https://github.com/WebAssembly/design
  8. Využití WebAssembly z programovacího jazyka Go
    https://www.root.cz/clanky/vyuziti-webassembly-z-programovaciho-jazyka-go/
  9. 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/
  10. List of languages that compile to JS
    https://github.com/jashke­nas/coffeescript/wiki/List-of-languages-that-compile-to-JS
  11. asm.js
    http://asmjs.org/
  12. Top 23 WASM Open-Source Projects
    https://www.libhunt.com/topic/wasm
  13. Made with WebAssembly
    https://madewithwebassembly.com/
  14. The Top 1,790 Wasm Open Source Projects on Github
    https://awesomeopensource­.com/projects/wasm
  15. Sanspiel
    https://sandspiel.club/
  16. Painting on HTML5 Canvas with Rust WASM
    https://www.subarctic.org/pa­inting_on_html5_canvas_wit­h_rust_wasm.html
  17. Writing WebAssembly By Hand
    https://blog.scottlogic.com/2018/04/26/we­bassembly-by-hand.html
  18. WebAssembly Specification
    https://webassembly.github­.io/spec/core/index.html
  19. Index of Instructions
    https://webassembly.github­.io/spec/core/appendix/in­dex-instructions.html
  20. The WebAssembly Binary Toolkit
    https://github.com/WebAssembly/wabt
  21. 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
  22. Webassembly as 32bit and 64bit
    https://stackoverflow.com/qu­estions/78580226/webassem­bly-as-32bit-and-64bit
  23. Portability
    https://webassembly.org/doc­s/portability/
  24. Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
    https://www.root.cz/clanky/he­xadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/
  25. Nástroj objdump: švýcarský nožík pro vývojáře
    https://www.root.cz/clanky/nastroj-objdump-svycarsky-nozik-pro-vyvojare/
  26. Getting Started: Building and Running Clang
    https://clang.llvm.org/get_star­ted.html
  27. Clang: a C language family frontend for LLVM
    https://clang.llvm.org/
  28. Scheduling LLVM Passes with the New Pass Manager
    https://stephenverderame.git­hub.io/blog/scheduling_llvm/
  29. C data types
    https://en.wikipedia.org/wi­ki/C_data_types
  30. WebAssembly data types
    https://webassembly.github­.io/spec/core/syntax/types­.html
  31. WebAssembly Opcodes
    https://pengowray.github.io/wasm-ops/
  32. Advanced tools (for WebAssembly)
    https://webassembly.org/getting-started/advanced-tools/
  33. Binaryen
    https://github.com/WebAssem­bly/binaryen

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.