WebAssembly 是一種在網路上執行程式碼的二進位格式,可實現快速啟動時間(與 JS 或 asm.js 相比,下載量更小,且在瀏覽器中解析速度更快)。Emscripten 預設會編譯為 WebAssembly,但您也可以為舊版瀏覽器編譯為 JS。
WebAssembly 預設會發出,不需要任何特殊標誌。
注意
如果您不想要 WebAssembly,您可以使用類似以下的語法停用它
emcc [..args..] -sWASM=0
注意
決定編譯為 Wasm 或 JS 可以在連結階段完成:它不會影響物件檔案。
自版本 1.39.0
(2019 年 10 月)起,Emscripten 使用上游 LLVM Wasm 後端發出 WebAssembly。先前,emscripten 也支援舊的 fastcomp 後端,該後端已在 2.0.0
(2020 年 8 月)中移除。
如果您從 fastcomp 升級到上游,您可能會注意到兩個後端之間的一些差異
Wasm 後端嚴格限制連結具有不同功能集的檔案 - 例如,如果一個檔案是使用原子操作建置的,而另一個檔案不是,則會在連結時發生錯誤。這可防止可能的錯誤,但也可能表示您需要進行一些建置系統修正。
WASM=0
在兩個後端中的行為不同。在 fastcomp 中,我們會發出 asm.js,而在上游中,我們會發出 JS(因為並非所有 Wasm 建構都可以在 asm.js 中表示)。此外,JS 支援會實作相同的外部 WebAssembly.*
API,因此特別是啟動預設會像 Wasm 一樣非同步,而且您可以使用 WASM_ASYNC_COMPILATION
來控制它(即使 WASM=0
)。
Wasm 後端預設會使用 Wasm 物件檔案。這表示它會在編譯步驟中進行程式碼產生,這會使連結步驟快得多 - 就像一般的原生編譯器一樣。為了比較,在 fastcomp 中,編譯步驟會在物件檔案中發出 LLVM IR。
您通常不會注意到這一點,但某些編譯器標誌會影響程式碼產生,例如 DISABLE_EXCEPTION_CATCHING
。這類標誌必須在程式碼產生期間傳遞。簡單且安全的方式是在編譯和連結時都傳遞所有 -s
標誌。
您可以使用一般的 llvm 標誌(-flto
、-flto=full
、-flto=thin
,在編譯和連結時都使用;但是請注意,薄 LTO 目前尚未經過大量測試,因此建議使用常規 LTO)來啟用連結時間最佳化 (LTO)。
使用 fastcomp 時,LTO 最佳化傳遞預設不會執行;為此,必須傳遞 --llvm-lto 1
。使用 llvm 後端時,LTO 傳遞將在任何位元碼格式的物件檔案上執行。
您可能會注意到的另一件事是,即使未設定 LTO,fastcomp 的連結階段也能執行一些次要的連結時間最佳化。LLVM 後端需要實際設定 LTO 才能執行這些操作。
Wasm 後端使用的連結器 wasm-ld 要求程式庫(.a 封存)包含符號索引。這符合原生 GNU 連結器的行為。emar 預設會建立這類索引,但原生工具(例如 GNU ar 和 GNU strip)並不知道 WebAssembly 物件格式,因此無法建立封存索引。特別是,如果您在包含 WebAssembly 物件檔案的封存檔案上執行 GNU strip,它將會移除索引,這會使封存在連結時無法使用。
另請參閱 Wasm 後端的封鎖器錯誤,以及Wasm 後端已標記的問題。
WebAssembly 可以在除以零、將非常大的浮點數捨入為整數等情況下捕獲 - 擲回例外狀況。在 asm.js 中,這類情況會被無聲地忽略,因為在 JavaScript 中它們不會擲回,因此這是您可能會注意到的 JavaScript 和 WebAssembly 之間的差異,瀏覽器會報告類似 float unrepresentable in integer range
、integer result unrepresentable
、integer overflow
或 Out of bounds Trunc operation
的錯誤。
LLVM Wasm 後端會避免捕獲,方法是在每個可能的捕獲周圍新增更多程式碼(基本上是如果會捕獲,則會鉗制值)。如果您不需要額外的程式碼,這可能會增加程式碼大小並降低速度。此問題的正確解決方案是使用較新的 Wasm 指令,這些指令不會捕獲,方法是使用 -mnontrapping-fptoint
呼叫 emcc 或 clang。不過,該程式碼可能無法在較舊的 VM 中執行。
當使用 emcc
建置 WebAssembly 時,你會看到一個包含該程式碼的 .wasm
檔案,以及主要的編譯目標 .js
檔案。這兩個檔案是設計成一起工作的:執行 .js
(或是 .html
,如果這是你要求的) 檔案,它會為你載入並設定 WebAssembly 程式碼,正確地設定其匯入和匯出等。基本上,你不需要關心編譯後的程式碼是 asm.js 還是 WebAssembly,這只是一個編譯器標誌,其他一切都應該正常運作 (除了 WebAssembly 應該會更快)。
請注意,.wasm
檔案並非獨立存在 - 如果沒有該 .js
程式碼,很難手動執行它,因為它需要與 JS 整合的正確匯入。例如,它會接收系統調用的匯入,以便它可以執行像列印到控制台之類的操作。目前正在努力開發建立獨立 .wasm
檔案的方法,請參閱 WebAssembly 獨立頁面。
你可能還會看到產生的其他檔案,例如,如果你預先載入檔案到虛擬檔案系統中,則會有一個 .data
檔案。所有這些都與建置 asm.js 時完全相同。你可能會注意到的一個差異是缺少 .mem file
,在 asm.js 中,它包含靜態記憶體初始化資料,而在 WebAssembly 中,我們可以更有效率地將這些資料封裝到 WebAssembly 二進位檔案本身中。
所有主要瀏覽器都支援 WebAssembly,包括 Firefox 52、Chrome 57、Safari 11 和 Opera 44 及更高版本。
有關各瀏覽器支援的 WebAssembly 功能的更多資訊,請參閱 WebAssembly 路線圖
.wasm
檔案和編譯¶WebAssembly 程式碼的準備方式與 asm.js 有些不同。asm.js 可以捆綁在主要的 JS 檔案中,而如前所述,WebAssembly 是一個獨立的二進位檔案,因此你將有多個檔案要分發。
另一個明顯的影響是,WebAssembly 預設是以非同步方式編譯的,這表示你必須等待編譯完成後才能呼叫編譯後的程式碼 (透過等待 main()
,或是 onRuntimeInitialized
回呼等等,當你有其他任何會使啟動變成非同步的操作時也需要這樣做,例如 asm.js 的 .mem
檔案,或是預先載入的檔案資料等等)。你可以透過設定 WASM_ASYNC_COMPILATION=0
來關閉非同步編譯,但由於目前 Chrome 的限制,這可能無法在 Chrome 中運作。
請注意,即使關閉了非同步編譯,擷取 WebAssembly 二進位檔案可能仍然需要非同步操作 (因為 Web 不允許在主執行緒上同步下載二進位檔案)。如果你可以自行擷取二進位檔案,你可以設定 Module['wasmBinary']
,它會從那裡使用,然後 (在關閉非同步編譯的情況下) 編譯應該是同步的。
為了以最有效的方式透過網路提供 Wasm,請確保你的網頁伺服器具有 .wasm
檔案的正確 MIME 類型,即 application/wasm。這將允許串流編譯,讓瀏覽器可以在下載時開始編譯程式碼。
在 Apache 中,你可以使用以下方法來實現:
AddType application/wasm .wasm
同時也請確認已啟用 gzip
AddOutputFilterByType DEFLATE application/wasm
如果你提供大型的 .wasm
檔案,網頁伺服器會在每個請求時動態壓縮它們,這會消耗 CPU 資源。相反地,你可以預先壓縮它們為 .wasm.gz
並使用內容協商
Options Multiviews
RemoveType .gz
AddEncoding x-gzip .gz
AddType application/wasm .wasm