要熟悉基本的堆疊操作,可從數值運算開始,目前經常看到的是將一個常數推入堆疊,也就是在 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 位元浮點數

