要熟悉基本的堆疊操作,可從數值運算開始,目前經常看到的是將一個常數推入堆疊,也就是在 i32
、i64
、f32
、f64
之後,接下 .const
。
i32.const
i64.const
f32.const
f64.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
視為不等於nan
f32.ne
:不相等,nan
視為不等於nan
f32.lt
:小於,nan
視為不等於nan
f32.le
:小於等於,nan
視為不等於nan
f32.gt
:大於,nan
視為不等於nan
f32.ge
:大於等於,nan
視為不等於nan
f32.min
:最小值,若運算元之一為nan
,傳回nan
f32.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 位元浮點數