大家也許會很好奇,用 wat2wasm 把文字轉成 wasm 檔的時候,跑出一大堆數字的部份是代表什麼意思?其實那就是轉換之後檔案裡實際的內容,也就是我們接下來要介紹的位元格式
雖然整數通常會有固定長度,像是 8, 16, 32, 64 bits,但是以 32 bits 來說,實際上常常不需要用到 32 bits 來表示。為了精減大小,WebAssembly 在位元格式上常常會使用 LEB128 格式來表示整數。
每個 byte 只用 7 bits 表示數值,剩下的 1 bit 表示是否結束
如果長度不夠,照有號/無號整數的方式補足長度,得到有號/無號整數的數值
以 32 位元整數來說,最多用到 5 個 byte,如果全都是 1 開頭的話表示錯誤;64 位元整數最多 10 個,16 位元最多 3 個,也是用這種方法檢查錯誤
雖然在數值接近最大值的時候,是用更多位元表示相同的數,不過大部分的狀況數值不會那麼大,所以用 LEB128 可以有效的降低檔案大小
| 型別 | 數值 |
|---|---|
| i32 | 0x7f |
| i64 | 0x7e |
| f32 | 0x7d |
| f64 | 0x7c |
接下來講解模組中各部份的位元格式,大家可以對照 wat2wasm 的輸出結果做印證
這部份固定佔 8 bytes,大部分的位元格式中幾乎都會有前文,用來區別不同的位元格式
- 魔術數字 (Magic number) 4 bytes
- 0x00 0x61 0x73 0x6d,用字碼轉換成文字之後就是 "\0asm",表示這是 wasm 格式
- 版本 (Version) 4 bytes
- 0x01 0x00 0x00 0x00,表示這是 0x01 版的 WebAssembly,在之前也有 0x0a, 0x0b, 0x0c, 0x0d的先行版本,之後如果推出新版的 WebAssembly,這邊就會變動
接下來會分成許多部份 (Section),對照到在 模組 章節一般格式的各個部份。每個部份都不是必須的,沒有用到的話就不會產生
每個部份在依開始都會有個標頭 (header),標示各個部份的代碼還有長度
-
代碼 (Section code)
用來區分是哪個部份
| 英文名稱 | 代碼 |
|---|---|
| Type | 1 |
| Import | 2 |
| Function | 3 |
| Table | 4 |
| Memory | 5 |
| Global | 6 |
| Export | 7 |
| Start | 8 |
| Element | 9 |
| Code | 10 |
| Data | 11 |
-
長度 (Section size)
用一個 LEB128 32 位元無號整數,表示整個部份的長度 (不包括標頭)
在 wat2wasm 的輸出裡會看到這邊是 section size (guess),既然是guess,表示這的值是亂猜的,不用理會,也不會被編進檔案裡
真正的值在下面 FIXUP section size 的地方,在編進檔案的時候也是這個真正值會被寫到section size (guess)的位置上
- 數量 (num types)
- 用一個 LEB128 32 位元無號整數,表示總共有多少函式型別
- 函式型別
| 欄位 | 數值或格式 | 補充說明 |
|---|---|---|
| 格式 | 0x60 (func) | 表示這是函數 |
| 參數總數 | LEB128 32 位元無號整數 | |
| 參數型別 | 數值型別格式 | 可能沒有,也可能有多個 |
| 回傳值總數 | LEB128 32 位元無號整數 | 目前只會是 0 或 1 |
| 回傳值型別 | 數值型別格式 | 0 ~ 1 個 |
- 數量 (num imports)
- 用一個 LEB128 32 位元無號整數,表示總共有多少引入物件
- 引入物件:
| 欄位 | 數值或格式 | 補充說明 |
|---|---|---|
| 字串長度 | LEB128 32 位元無號整數 | 模組名稱的字串長度 |
| 模組名稱 | 字串 | 以字碼表示 |
| 字串長度 | LEB128 32 位元無號整數 | 輸出名稱的字串長度 |
| 輸出名稱 | 字串 | 以字碼表示 |
| 引入種類 | 8 位元無號整數 | 0: 函式, 1:函式表, 2:記憶體, 3:全域變數 |
在引入物件中,接下來的部份會依據不同的引入種類,而有不同的格式:
- 函式
- 一個 LEB128 32 位元無號整數,表示函式型別的編號
- 函式表
| 欄位 | 數值或格式 | 補充說明 |
|---|---|---|
| 元素型別 | 0x70 (anyfunc) | 目前函式表只支援這個型別 |
| 標記 | 0 或 1 | 0:沒有最大值, 1:有最大值 |
| 起始大小 | LEB128 32 位元無號整數 | |
| 最大值 | LEB128 32 位元無號整數 | 有最大值才有 |
- 記憶體
| 欄位 | 數值或格式 | 補充說明 |
|---|---|---|
| 標記 | 0 或 1 | 0:沒有最大值, 1:有最大值 |
| 起始大小 | LEB128 32 位元無號整數 | 以 page 為單位 |
| 最大值 | LEB128 32 位元無號整數 | 有最大值才有 |
- 全域變數
| 欄位 | 數值或格式 | 補充說明 |
|---|---|---|
| 數值型別 | 數值型別格式 | |
| 可變動性 | 0 或 1 | 0:不可變動, 1:可變動 |
- 數量 (num functions)
- 用一個 LEB128 32 位元無號整數,表示總共有多少函式
- 編號
- 一個或多個 LEB128 32 位元無號整數,表示該函式使用的函式型別編號
- 數量 (num tables)
- 用一個 LEB128 32 位元無號整數,表示總共有多少函式表 (目前只會是 1)
- 函式表
| 欄位 | 數值或格式 | 補充說明 |
|---|---|---|
| 元素型別 | 0x70 (anyfunc) | 目前函式表只支援這個型別 |
| 標記 | 0 或 1 | 0:沒有最大值, 1:有最大值 |
| 起始大小 | LEB128 32 位元無號整數 | |
| 最大值 | LEB128 32 位元無號整數 | 有最大值才有 |
- 數量 (num memories)
- 用一個 LEB128 32 位元無號整數,表示總共有多少記憶體 (目前只會是 1)
- 記憶體
| 欄位 | 數值或格式 | 補充說明 |
|---|---|---|
| 標記 | 0 或 1 | 0:沒有最大值, 1:有最大值 |
| 起始大小 | LEB128 32 位元無號整數 | 以 page 為單位 |
| 最大值 | LEB128 32 位元無號整數 | 有最大值才有 |
- 數量 (num globals)
- 用一個 LEB128 32 位元無號整數,表示總共有多少全域變數
- 全域變數
| 欄位 | 數值或格式 | 補充說明 |
|---|---|---|
| 數值型別 | 數值型別格式 | |
| 可變動性 | 0 或 1 | 0:不可變動, 1:可變動 |
| 起始值指令 | 指令 | 請參考下面"指令"小節,只能是常數指令 |
| 結束 | byte | 用一個 0x0b (end) 指令表示結束 |
- 數量 (num exports)
- 用一個 LEB128 32 位元無號整數,表示總共有多少全域變數
- 輸出物件
| 欄位 | 數值或格式 | 補充說明 |
|---|---|---|
| 長度 | LEB128 32 位元無號整數 | 輸出名稱長度 |
| 輸出名稱 | 字串 | 以字碼表示 |
| 輸出種類 | 8 位元無號整數 | 0: 函式, 1:函式表, 2:記憶體, 3:全域變數 |
| 位址 | LEB128 32 位元無號整數 | 在儲存空間中輸出物件的位址 (Ex: 種類是函式,表示在儲存空間中的第幾號函式) |
- 索引值 (index)
- 一個 LEB128 32 位元無號整數,表示起始函式在模組中的編號
- 數量 (num elements)
- 用一個 LEB128 32 位元無號整數,表示總共有多少函式表元素
- 函式表元素
- 會根據這邊的資料,指定函式表的值
| 欄位 | 數值或格式 | 補充說明 |
|---|---|---|
| 索引值 | LEB128 32 位元無號整數 | 函式表的編號(目前只會是 0) |
| 位移 | 指令 | 請參考下面"指令"小節,只能是常數指令 |
| 元素數量 | LEB128 32 位元無號整數 | 表示有幾個元素要指定 |
| 元素 | LEB128 32 位元無號整數 |
|
程式碼部份表示函式的內容
- 數量 (num functions)
- 用一個 LEB128 32 位元無號整數,表示總共有多少函式內容
- 函式內容
| 欄位 | 數值或格式 | 補充說明 |
|---|---|---|
| 長度 | LEB128 32 位元無號整數 | 函式內容的總長度 |
| 區域變數數量 | LEB128 32 位元無號整數 | 區域變數的總數 |
| 區域變數 | 區域變數 |
|
| 函式內容 | byte |
|
| 結束 | byte | 一個 0x0b (end) 指令表示結束 |
-
區域變數 (在函式內容裏面)
相同種類且接連在一起宣告的區域變數,會合併成一個區域變數格式,所以才會有"數量"欄位
| 欄位 | 數值或格式 | 補充說明 |
|---|---|---|
| 數量 | LEB128 32 位元無號整數 | 表示要宣告幾個區域變數 |
| 數值型別 | 數值型別格式 |
資料部份表示記憶體的初始資料
- 數量 (num data)
- 用一個 LEB128 32 位元無號整數,表示總共有多少資料指定物件
- 資料指定物件
| 欄位 | 數值或格式 | 補充說明 |
|---|---|---|
| 索引值 | LEB128 32 位元無號整數 | 記憶體的編號(目前只會是 0) |
| 位移 | 指令 | 請參考下面"指令"小節,只能是常數指令 |
| 長度 | LEB128 32 位元無號整數 | 表示要指定的資料總長度 |
| 資料 | byte |
|
指令的部份大部份是一個 byte 編碼,有些指令會加上額外的參數
| 指令 | byte 編碼 | 額外參數 |
|---|---|---|
| unreachable | 0x00 | |
| nop | 0x01 | |
| block | 0x02 | 一個數值型別表示回傳值,或 0x40 表示沒回傳值 |
| loop | 0x03 | 一個數值型別表示回傳值,或 0x40 表示沒回傳值 |
| if | 0x04 | 一個數值型別表示回傳值,或 0x40 表示沒回傳值 |
| else | 0x05 | |
| end | 0x0b | |
| br | 0x0c | 跳回層數 : LEB128 32 位元無號整數 |
| br_if | 0x0d | 跳回層數 : LEB128 32 位元無號整數 |
| br_table | 0x0e | 請見下面 "br_table 額外參數" |
| return | 0x0f | |
| call | 0x10 | 函式編號 : LEB128 32 位元無號整數 |
| call_indirect | 0x11 | 函式型別編號 : LEB128 32 位元無號整數 ;保留值 : 0x00 |
- br_table 額外參數
- 跳回層數數量 : 一個 LEB128 32 位元無號整數
- 跳回層數 :
$$\ge 1$$ 個 LEB128 32 位元無號整數
| 指令 | byte 編碼 | 額外參數 |
|---|---|---|
| drop | 0x1a | |
| select | 0x1b | |
| get_local | 0x20 | 區域變數編號 : LEB128 32 位元無號整數 |
| set_local | 0x21 | 區域變數編號 : LEB128 32 位元無號整數 |
| tee_local | 0x22 | 區域變數編號 : LEB128 32 位元無號整數 |
| get_global | 0x23 | 全域變數編號 : LEB128 32 位元無號整數 |
| set_global | 0x24 | 全域變數編號 : LEB128 32 位元無號整數 |
| 指令 | byte 編碼 | 額外參數 |
|---|---|---|
| i32.load | 0x28 | 請見下面"記憶體額外參數" |
| i64.load | 0x29 | 請見下面"記憶體額外參數" |
| f32.load | 0x2a | 請見下面"記憶體額外參數" |
| f64.load | 0x2b | 請見下面"記憶體額外參數" |
| i32.load8_s | 0x2c | 請見下面"記憶體額外參數" |
| i32.load8_u | 0x2d | 請見下面"記憶體額外參數" |
| i32.load16_s | 0x2e | 請見下面"記憶體額外參數" |
| i32.load16_u | 0x2f | 請見下面"記憶體額外參數" |
| i64.load8_s | 0x30 | 請見下面"記憶體額外參數" |
| i64.load8_u | 0x31 | 請見下面"記憶體額外參數" |
| i64.load16_s | 0x32 | 請見下面"記憶體額外參數" |
| i64.load16_u | 0x33 | 請見下面"記憶體額外參數" |
| i64.load32_s | 0x34 | 請見下面"記憶體額外參數" |
| i64.load32_u | 0x35 | 請見下面"記憶體額外參數" |
| i32.store | 0x36 | 請見下面"記憶體額外參數" |
| i64.store | 0x37 | 請見下面"記憶體額外參數" |
| f32.store | 0x38 | 請見下面"記憶體額外參數" |
| f64.store | 0x39 | 請見下面"記憶體額外參數" |
| i32.store8 | 0x3a | 請見下面"記憶體額外參數" |
| i32.store16 | 0x3b | 請見下面"記憶體額外參數" |
| i64.store8 | 0x3c | 請見下面"記憶體額外參數" |
| i64.store16 | 0x3d | 請見下面"記憶體額外參數" |
| i64.store32 | 0x3e | 請見下面"記憶體額外參數" |
| current_memory | 0x3f | 保留值 : 0x00 |
| grow_memory | 0x40 | 保留值 : 0x00 |
- 記憶體額外參數
-
對齊 (alignment) : 一個 LEB128 32 位元無號整數
(WasmVM 不會用到這個值)
-
位移 (offset) : 一個 LEB128 32 位元無號整數
-
| 指令 | byte 編碼 | 額外參數 |
|---|---|---|
| i32.const | 0x41 | 數值 : LEB128 32 位元有號整數 |
| i64.const | 0x42 | 數值 : LEB128 64 位元有號整數 |
| f32.const | 0x43 | 數值 : 4 個 byte 表示單精度浮點數 |
| f64.const | 0x44 | 數值 : 8 個 byte 表示雙精度浮點數 |
| 指令 | byte 編碼 | 額外參數 |
|---|---|---|
| i32.eqz | 0x45 | |
| i32.eq | 0x46 | |
| i32.ne | 0x47 | |
| i32.lt_s | 0x48 | |
| i32.lt_u | 0x49 | |
| i32.gt_s | 0x4a | |
| i32.gt_u | 0x4b | |
| i32.le_s | 0x4c | |
| i32.le_u | 0x4d | |
| i32.ge_s | 0x4e | |
| i32.ge_u | 0x4f | |
| i64.eqz | 0x50 | |
| i64.eq | 0x51 | |
| i64.ne | 0x52 | |
| i64.lt_s | 0x53 | |
| i64.lt_u | 0x54 | |
| i64.gt_s | 0x55 | |
| i64.gt_u | 0x56 | |
| i64.le_s | 0x57 | |
| i64.le_u | 0x58 | |
| i64.ge_s | 0x59 | |
| i64.ge_u | 0x5a | |
| f32.eq | 0x5b | |
| f32.ne | 0x5c | |
| f32.lt | 0x5d | |
| f32.gt | 0x5e | |
| f32.le | 0x5f | |
| f32.ge | 0x60 | |
| f64.eq | 0x61 | |
| f64.ne | 0x62 | |
| f64.lt | 0x63 | |
| f64.gt | 0x64 | |
| f64.le | 0x65 | |
| f64.ge | 0x66 | |
| i32.clz | 0x67 | |
| i32.ctz | 0x68 | |
| i32.popcnt | 0x69 | |
| i32.add | 0x6a | |
| i32.sub | 0x6b | |
| i32.mul | 0x6c | |
| i32.div_s | 0x6d | |
| i32.div_u | 0x6e | |
| i32.rem_s | 0x6f | |
| i32.rem_u | 0x70 | |
| i32.and | 0x71 | |
| i32.or | 0x72 | |
| i32.xor | 0x73 | |
| i32.shl | 0x74 | |
| i32.shr_s | 0x75 | |
| i32.shr_u | 0x76 | |
| i32.rotl | 0x77 | |
| i32.rotr | 0x78 | |
| i64.clz | 0x79 | |
| i64.ctz | 0x7a | |
| i64.popcnt | 0x7b | |
| i64.add | 0x7c | |
| i64.sub | 0x7d | |
| i64.mul | 0x7e | |
| i64.div_s | 0x7f | |
| i64.div_u | 0x80 | |
| i64.rem_s | 0x81 | |
| i64.rem_u | 0x82 | |
| i64.and | 0x83 | |
| i64.or | 0x84 | |
| i64.xor | 0x85 | |
| i64.shl | 0x86 | |
| i64.shr_s | 0x87 | |
| i64.shr_u | 0x88 | |
| i64.rotl | 0x89 | |
| i64.rotr | 0x8a | |
| f32.abs | 0x8b | |
| f32.neg | 0x8c | |
| f32.ceil | 0x8d | |
| f32.floor | 0x8e | |
| f32.trunc | 0x8f | |
| f32.nearest | 0x90 | |
| f32.sqrt | 0x91 | |
| f32.add | 0x92 | |
| f32.sub | 0x93 | |
| f32.mul | 0x94 | |
| f32.div | 0x95 | |
| f32.min | 0x96 | |
| f32.max | 0x97 | |
| f32.copysign | 0x98 | |
| f64.abs | 0x99 | |
| f64.neg | 0x9a | |
| f64.ceil | 0x9b | |
| f64.floor | 0x9c | |
| f64.trunc | 0x9d | |
| f64.nearest | 0x9e | |
| f64.sqrt | 0x9f | |
| f64.add | 0xa0 | |
| f64.sub | 0xa1 | |
| f64.mul | 0xa2 | |
| f64.div | 0xa3 | |
| f64.min | 0xa4 | |
| f64.max | 0xa5 | |
| f64.copysign | 0xa6 | |
| i32.wrap/i64 | 0xa7 | |
| i32.trunc_s/f32 | 0xa8 | |
| i32.trunc_u/f32 | 0xa9 | |
| i32.trunc_s/f64 | 0xaa | |
| i32.trunc_u/f64 | 0xab | |
| i64.extend_s/i32 | 0xac | |
| i64.extend_u/i32 | 0xad | |
| i64.trunc_s/f32 | 0xae | |
| i64.trunc_u/f32 | 0xaf | |
| i64.trunc_s/f64 | 0xb0 | |
| i64.trunc_u/f64 | 0xb1 | |
| f32.convert_s/i32 | 0xb2 | |
| f32.convert_u/i32 | 0xb3 | |
| f32.convert_s/i64 | 0xb4 | |
| f32.convert_u/i64 | 0xb5 | |
| f32.demote/f64 | 0xb6 | |
| f64.convert_s/i32 | 0xb7 | |
| f64.convert_u/i32 | 0xb8 | |
| f64.convert_s/i64 | 0xb9 | |
| f64.convert_u/i64 | 0xba | |
| f64.promote/f32 | 0xbb | |
| i32.reinterpret/f32 | 0xbc | |
| i64.reinterpret/f64 | 0xbd | |
| f32.reinterpret/i32 | 0xbe | |
| f64.reinterpret/i64 | 0xbf |


