WebAssembly 的主要運算會在堆疊上進行,想要執行某個指令,必須先在堆疊中推入數值,然後執行指令,指令會從堆疊頂端取出必要的數值個數後執行,執行結果是否置回堆疊依個別指令而定。
例如,若要進行 1 + 2 的運算,必須在堆疊中推入 1,接著推入 2,然後執行加法指令,這會取出堆疊頂端兩個數字,相加後置回堆疊。
當然,真正處理的都是位元,置入堆疊的是一組位元,從堆疊中取出的也是一組位元,對於這組位元要以什麼觀點來看得,就是資料型態的概念,有了資料型態的概念,就可以用具體的概念來操作一組位元,而不是直接處理 0101 的運算。
就目前來說,WebAssembly 在執行指令時主要分為四種觀點,或說是四種資料型態:
i32
:32 位元整數i64
:64 位元整數f32
:32 位元浮點數f64
:64 位元浮點數
你也許會問,整數是有號還是無號?堆疊中保留的是位元形式,指令會使用某個資料型態的觀點,將一組位元置入堆疊,或者從堆疊中取出一組位元,因此問題不在於整數是有號或無號,而是要看你使用什麼指令。
例如,要將一個數字以 32 位元整數推入堆疊,可以使用 i32.const
指令:
(module
(func $main
i32.const 2147483647
drop
)
(start $main)
)
2147483647 以二進位表示會是 1111111 11111111 11111111 11111111,i32.const
將 2147483647 以 32 個位元整數看待,因此會將 01111111 11111111 11111111 11111111(以 Little-endian)放入堆疊。
drop
指令是從堆疊中取出一個數值,然後捨棄不用,這是因為這個模組只執行一個函式,函式是一種區塊,而且範例中的函式沒有定義結果型態,此時執行過後堆疊必須是空,否則會發生堆疊非空的錯誤。
如果數字是 2147483648 呢?
(module
(func $main
i32.const 2147483648
drop
)
(start $main)
)
2147483648 以二進位表示會是 10000000 00000000 00000000 00000000,i32.const
將 2147483648 以 32 個位元整數看待,因此會將 10000000 00000000 00000000 00000000(以 Little-endian)放入堆疊,因此,上面的程式碼等同於:
(module
(func $main
i32.const -2147483648
drop
)
(start $main)
)
因為 WebAssembly 在表示負數時,會使用 2 的補數,因此 -2147483648 的二進位表示,會是 10000000 00000000 00000000 00000000。
如果將範例中的 i32
改為 i64
:
(module
(func $main
i64.const 2147483648
drop
)
(start $main)
)
2147483648 以二進位表示會是 10000000 00000000 00000000 00000000,i64.const
將 2147483648 以 64 個位元整數看待,因此會將 00000000 00000000 00000000 00000000 10000000 00000000 00000000 00000000 放入堆疊。
如果資料必須與 JavaScript 環境溝通時,必須留意的是,因為 JavaScript 沒有整數與浮點數的區別,數值都是 〈IEEE 754 標準 64 位元浮點數〉,因而用於整數表示的話,可表達的整數為 -253 到 253 之間(不包含 -253 與 253),因此與 JavaScript 環境溝通時不能使用 i64
,例如底下會出錯:
(module
(import "env" "log" (func $log (param i64)))
(func $main
i64.const 2147483648
call $log
)
(start $main)
)
使用 f64
就不會有問題:
(module
(import "env" "log" (func $log (param f64)))
(func $main
f64.const 2147483648
call $log
)
(start $main)
)
在數字的撰寫上,除了 10 進位方式之外,可以使用 0x
前置來撰寫 16 進位數字,浮點數的部份,可以使用科學記號表示,inf
表示無限,nan
表示 NaN(Not a number)。