要熟悉基本的堆疊操作,可從數值運算開始,目前經常看到的是將一個常數推入堆疊,也就是在 i32、i64、f32、f64 之後,接下 .const。
i32.consti64.constf32.constf64.const
需要在堆疊中放入幾個數值,視接下來要進行的運算而定,底下的指令運算結果會置入堆疊。
整數運算
在整數運算方面,i32 與 i64 擁有相同的指令集,例如 i32.add 就有對應的 i64.add,常見的加減乘除等是二元運算,以 i32 為例,二元運算中與常見數學運算相關的指令有:
i32.add:相加i32.sub:相減i32.mul:相乘i32.div_s:有號相除,捨去小數i32.div_u:無號相除,採用 floor 捨入i32.rem_s:有號餘除(結果採被除數之正負)i32.rem_u:無號餘除
與位元運算相關的指令有:
i32.and:AND 位元運算i32.or:OR 位元運算i32.xor:XOR 位元運算i32.shl:位元左移i32.shr_u:補 0 位元右移i32.shr_s:補最左位元之位元右移i32.rotl:循環位元左移i32.rotr:循環位元右移
與比較運算相關的指令有底下,在 WebAssembly 中,成立都是輸出 1,不成立為 0:
i32.eq:相等i32.ne:不相等i32.lt_s:有號小於i32.le_s:有號小於等於i32.lt_u:無號小於i32.le_u:有號小於等於i32.gt_s:有號大於i32.ge_s:有號大於等於i32.gt_u:無號大於i32.ge_u:無號大於等於
因為是二元運算,堆疊中必須先置入兩個數值,再來呼叫以上的指令,例如 1 + 2:
(module
(import "env" "log" (func $log (param i32)))
(func $main
i32.const 1
i32.const 2
i32.add
call $log ;; 在 conole 顯示 3
)
(start $main)
)
在這邊看到註解的另一個形式,也就是在 ;; 之後加上註解文字,這在編譯時會被忽略掉。
實際上,如果已經熟悉堆疊操作上的順序,可以寫為:
(module
(import "env" "log" (func $log (param i32)))
(func $main
(i32.add (i32.const 1) (i32.const 2))
call $log
)
(start $main)
)
括號內的會先執行,可以回顧一下〈從 C 到 WebAssembly〉中,轉譯出來的 Wat,就有這類的寫法,進一步地觀察它,你就可以寫出:
(module
(import "env" "log" (func $log (param i32)))
(func $main
(call $log
(i32.add (i32.const 1) (i32.const 2))
)
)
(start $main)
)
簡單來說,先習慣堆疊操作邏輯,有機會也多觀察一下 C 轉譯出的的 Wat 長什麼樣,就會寫出更自然的程式碼了,基於這一系列文件是入門的基礎,之後多半會使用較基本的寫法,若對可讀性有很大幫助,才會使用如上較自然的寫法。
如果是一元運算,只要在堆疊中置入一個數值,就可以呼叫以下指令:
i32.clz:左邊有幾個 0 的位元i32.ctz:右邊有幾個 0 的位元i32.popcn:有幾個 1 的位元i32.eqz:是否為 0
浮點數運算
在浮點數方面,f32 與 f64 擁有相同的指令集,以 f32 為例,其中二元運算的指令有:
f32.add:相加f32.sub:相減f32.mul:相乘f32.div:相除f32.copysign:令左運算元(先置入堆疊的值)正負號與右運算元相同f32.eq:相等,nan視為不等於nanf32.ne:不相等,nan視為不等於nanf32.lt:小於,nan視為不等於nanf32.le:小於等於,nan視為不等於nanf32.gt:大於,nan視為不等於nanf32.ge:大於等於,nan視為不等於nanf32.min:最小值,若運算元之一為nan,傳回nanf32.max:最大值,若運算元之一為nan,傳回nan
一元運算的指令有:
f32.abs:絕對值f32.neg:改變正負號f32.ceil:ceil 捨入f32.floor:floor 捨入f32.trunc:round 至最接近 0 的整數f32.nearest:round 至最接近的偶數f32.sqrt:平方根
(有關於 round、ceil、round,可參考〈算錢學問大〉。)
型別轉換運算
不同型態的數值,會有放在一起運算的機會,這時必須進行適當的型態轉換,在整數部份,i32 擁有底下的型態轉換指令:
i32.wrap/i64:將 64 位元整數轉 32 位元整數,多的部份捨去i32.trunc_s/f32:將 32 位元浮點數轉為有號 32 位元整數i32.trunc_s/f64:將 64 位元浮點數轉為有號 32 位元整數i32.trunc_u/f32:將 32 位元浮點數轉為無號 32 位元整數i32.trunc_u/f64:將 64 位元浮點數轉為無號 32 位元整數i32.reinterpret/f32:將 32 位元浮點數的位元組重新詮釋為 32 位元整數
一個例子如下,拿掉 i32.wrap/i64 的話,i32.add 會因型態不符而出錯:
(module
(import "env" "log" (func $log (param i32)))
(func $main
i32.const 1
i64.const 2
i32.wrap/i64
i32.add
call $log
)
(start $main)
)
i64 擁有底下的型態轉換指令:
i64.extend_s/i32:將 32 位元整數擴充為 64 位元有號整數i64.extend_u/i32:將 32 位元整數擴充為 64 位元無號整數i64.trunc_s/f32:將 32 位元浮點數轉為有號 64 位元整數i64.trunc_s/f64:將 64 位元浮點數轉為有號 64 位元整數i64.trunc_u/f32:將 32 位元浮點數轉為無號 64 位元整數i64.trunc_u/f64:將 64 位元浮點數轉為無號 64 位元整數i64.reinterpret/f64:將 64 位元浮點數的位元組重新詮釋為 64 位元整數
f32 擁有底下的型態轉換指令:
f32.demote/f64:將 64 位元浮點數降為 32 位元浮點數f32.convert_s/i32:將 32 位元有號整數轉為 32 位元浮點數f32.convert_s/i64:將 64 位元有號整數轉為 32 位元浮點數f32.convert_u/i32:將 32 位元無號整數轉為 32 位元浮點數f32.convert_u/i64:將 64 位元無號整數轉為 32 位元浮點數f32.reinterpret/i32:將 32 位元整數的位元組重新詮釋為 32 位元浮點數
f64 擁有底下的型態轉換指令:
f64.promote/f32:將 32 位元浮點數升為 64 位元浮點數f64.convert_s/i32:將 32 位元有號整數轉為 64 位元浮點數f64.convert_s/i64:將 64 位元有號整數轉為 64 位元浮點數f64.convert_u/i32:將 32 位元無號整數轉為 64 位元浮點數f64.convert_u/i64:將 64 位元無號整數轉為 64 位元浮點數f64.reinterpret/i64:將 64 位元整數的位元組重新詮釋為 64 位元浮點數

