WebAssembly 檔案格式
編譯完成的 WebAssembly 二進位模組。
- MIME 類型
- application/wasm
- 副檔名
- .wasm
- 分類
- 執行檔與位元碼
Magic number(檔案簽章)
| 0x00000000 | 0061736D | ·asm | 0x00 'asm' |
關於 WebAssembly 格式
WebAssembly 模組是這份目錄裡最規律的二進位格式:8 位元組的前導,接著一連串 section,每個 section 是一個位元組的 id、一個 LEB128 長度,然後是那麼多個位元組的內容。位元組 8 之後沒有任何東西在固定位移上,但也不需要——讀取端逐個走過 section,不在乎的可以直接跳過,這正是瀏覽器能一邊下載模組其餘部分、一邊開始編譯的原因。已知的 section 必須依固定順序出現,所以驗證器不必跑第二遍就能拒收格式錯誤的模組。
檔頭結構,一個位元組一個位元組看
| 位移 | 長度 | 欄位 | 意義 |
|---|---|---|---|
| 0 | 4 | magic | 00 61 73 6D——一個 NUL 位元組後面接 ASCII 的 asm。開頭那個 NUL 是刻意的:它保證這個檔案不是合法的文字,所以只要模組經過任何會動到文字編碼的東西,它會在第一個位元組就失敗,而不是在某個難以預料的地方出事。 |
| 4 | 4 | version | 32 位元小端序的版本號,目前是 01 00 00 00,也就是版本 1,從這個格式問世以來就沒變過,因為之後的每個擴充都做成向後相容,而不是把這個數字往上加。 |
| 8 | 1 | First section id | 一個位元組指名 section:0 custom、1 type、2 import、3 function、4 table、5 memory、6 global、7 export、8 start、9 element、10 code、11 data、12 data count。已知 section 必須遞增排列、各最多出現一次,所以前導之後的第一個位元組通常是 1(type)或 0(工具鏈塞在最前面的 custom section)。 |
| 9 | variable | Section size (LEB128) | 無號 LEB128 的 section 內容位元組數——一到五個位元組,每個帶 7 位元的值,最高位元是「還有下一個」的旗標。這是整個格式裡唯一的長度欄位,每個 section 邊界都是靠加上它算出來的,所以位移 8 之後沒有任何東西有固定位址。 |
| variable | variable | Section body | 由項目組成的向量,向量本身也帶著 LEB128 的數量前綴。下一個 section id 緊接在長度欄位所宣稱的最後一個位元組之後開始,所以把所有 id 與長度加總,必須剛好落在檔案結尾——這是這個格式內建的截斷檢查。 |
| variable | variable | Custom sections (id 0) | 一段具名的資料,沒有任何語意:包含放函式名稱給堆疊追蹤用的 'name' section、DWARF 除錯資訊、產生器中繼資料,以及 target-features 記錄。它們可以出現在其他 section 之間的任何位置,而 release 建置要縮小模組,做的就是把它們剝掉。 |
怎麼跟長得像的格式分開
| 容易搞混的 | 怎麼分 |
|---|---|
| WAT text | 同一個模組的文字形式是原始碼,不是二進位的變體:它以左括號加上 module 這個字開頭,全文都是 UTF-8。開頭是 00 61 73 6D 的就是二進位形式,而瀏覽器只實體化這一種。 |
| GZIP | 實務上最常見的混淆,因為 .wasm 幾乎一定是壓縮後才送出來的。開頭是 1F 8B 的檔案是 gzip 資料流,wasm 的 magic 要解壓縮之後才出現。直接從網路分頁存下來的建置產物,常常是掛著 .wasm 名字的 gzip。 |
| ELF 執行檔 | 兩者都是給虛擬機器或真實機器的編譯產出,而 emscripten 或 wasi-sdk 的建置目錄裡兩種都有——.o 檔是 ELF,只有最終模組是 wasm。位移 0 的 7F 45 4C 46 對上 00 61 73 6D,分得清清楚楚。 |
| asm.js | 它的前身,在舊的打包產物裡還找得到:asm.js 就是 JavaScript,一個帶著 'use asm' 指示詞的純文字檔,以 .js 交付。工具要是拿 WebAssembly.instantiate 去載它,會卡在 magic,因為裡面根本沒有二進位模組。 |
常見的壞法
- 被截斷。section 大小是宣告在內容之前的,所以下載一半的檔案會一路正確解析,直到某個 section 宣稱的位元組數超過剩下的量——解碼器會報 'unexpected end of section or function',這是這份目錄裡所有格式中最清楚的截斷訊息。
- 以 gzip 壓縮的形式送出或存下來。用 Content-Encoding gzip 下載、原封不動寫到磁碟的 .wasm 開頭是 1F 8B,WebAssembly.instantiate 會卡在 magic 拒收。位元組本身沒問題,錯的只是層次。
- section 內容裡有一個位元組跑掉。只要長度欄位還完好,section 走訪照樣落在正確的邊界上,所以模組常常載得起來,然後在執行期 trap 或行為異常,這比標頭失敗難診斷得多。
- 文字模式傳輸,或編輯器在檔尾補了一個換行。多出來的一個位元組會讓 section 走訪結束在離檔尾差一個位元組的地方,嚴格的解碼器會回報有多餘的尾端位元組,而不是接受它。
這個格式現在還在用嗎
WebAssembly 是既有原生程式碼進入瀏覽器的方式:影片與影像編解碼器、PDF 與 CAD 引擎、SQLite、Python 與 Ruby 執行環境、模擬器,以及瀏覽器內壓縮工具背後的壓縮函式庫,全都以 .wasm 模組出貨。它不是 JavaScript 的替代品,也不拿來做 UI——它自己碰不到 DOM——但只要是必須在使用者機器上跑的計算密集工作,預設答案就是它,而這也正是「純瀏覽器端、不用上傳」的工具能成立的原因。在瀏覽器之外它長出了第二種生命,變成可攜的沙箱:WASI 執行環境、邊緣運算平台與外掛系統用的都是同一種二進位格式,所以替網站建置的模組常常原封不動就能在伺服器上跑。
在這個站上打開 WebAssembly 檔
這些全部在你的瀏覽器裡跑,檔案不會離開你的電腦。
相關格式
容易和 WebAssembly 搞混的格式,可以比對它們的開頭位元組。
| 格式 | 開頭位元組 | 副檔名 |
|---|---|---|
| ELF 執行檔 | 7F 45 4C 46 | .elf |
| Java class 檔 | CA FE BA BE | .class |